diff --git a/.env b/.env new file mode 100644 index 0000000..dd26fb0 --- /dev/null +++ b/.env @@ -0,0 +1,12 @@ +IDENTITY_SERVER_URL=https://id.leanda.io/auth/realms/OSDR +CORE_API_URL=http://localhost:28611/api +BLOB_STORAGE_API_URL=http://localhost:18006/api +IMAGING_URL=http://localhost:7972/api +SIGNALR_URL=http://localhost:28611/signalr +METADATA_URL=http://localhost:63790/api +PROXY_JSMOL_URL=http://localhost:28611/api/proxy/jsmol +KETCHER_URL=https://osdr.dev.dataledger.io/ketcher/indigo/layout +OSDR_LOG_FOLDER=logs +OSDR_TEMP_FILES_FOLDER=./temp +REALM=OSDR +OSDR_LOG_LEVEL=Debug \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..74a7465 --- /dev/null +++ b/.gitignore @@ -0,0 +1,67 @@ +################################################################################ +# This .gitignore file was automatically created by Microsoft(R) Visual Studio. +################################################################################ + +OsdrWebDeploy/ +source/packages/ + +# Build results +[Dd]ebug/ +[Rr]elease/ +x64/ +[Bb]uild/ +!Source/Osdr.Mvc/OsdrWeb/Widgets/3rd/pdfjs/build +!Source/Osdr.Ng2/src/client/assets/3rd/pdf-js/build +!Source/Osdr.Mvc/3rd/ +[Bb]in/ +[Oo]bj/ +[Dd]ist/ +[Xx]unit/ +[Xx]unit-results.xml + +# User-specific files +*.suo +*.user +*.sln.docstates +.vs/ +.vscode/ +project.lock.json + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +# Node/Bower +bower_components/ +node_modules/ + +.DS_Store +._.DS_Store +._* + +#NuGet packages +*.nupkg + +# Auto-generated JS, CSS files +packages/ + +!Source/Services/Imaging/Sds.Imaging.Processing/bin/gsdll32.dll +!Source/Services/Imaging/Sds.Imaging.Processing/bin/gsdll64.dll +!Source/Services/Imaging/Sds.Imaging.Processing/bin/Select.Html.dep +!Source/Services/Imaging/Sds.Imaging.Rasterizers/bin/gsdll32.dll +!Source/Services/Imaging/Sds.Imaging.Rasterizers/bin/gsdll64.dll +!Source/Services/Imaging/Sds.Imaging.Rasterizers/bin/Select.Html.dep +/Source/Osdr.Services/Logging/elasticsearch +/Source/Services/Logging/elasticsearch +/Source/Services/OsdrService/Sds.Osdr.Domain.BddTests/Resources/Aspirin.mol.svg diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d6c694d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,81 @@ +language: minimal + +services: + - docker + +branches: + only: + - master + +os: + - linux + +env: + global: + - DATE=`TZ=America/New_York date "+%Y-%m-%d-%H%M"` + - DOCKER_CACHE_FILE=/home/travis/docker/docker-cache.tar + - LEANDA_PERSISTENCE_IMAGE="leanda/core-persistence" + - LEANDA_FRONTEND_IMAGE="leanda/core-frontend" + - LEANDA_BACKEND_IMAGE="leanda/core-backend" + - LEANDA_SAGAHOST_IMAGE="leanda/core-sagahost" + - LEANDA_WEBAPI_IMAGE="leanda/core-web-api" + - LEANDA_WEBAPI_INTEGRATION_TESTS_IMAGE="leanda/webapi-integration" + +cache: + directories: + - /home/travis/docker/ + +jobs: + include: + - stage: Build + name: Build Leanda Back-End, Front-End, Sagahost, Persistence and Web API services + script: + - docker build -t $LEANDA_PERSISTENCE_IMAGE:ci -f Sds.Osdr.Persistence/Dockerfile . + - docker build -t $LEANDA_FRONTEND_IMAGE:ci -f Sds.Osdr.Domain.FrontEnd/Dockerfile . + - docker build -t $LEANDA_BACKEND_IMAGE:ci -f Sds.Osdr.Domain.BackEnd/Dockerfile . + - docker build -t $LEANDA_SAGAHOST_IMAGE:ci -f Sds.Osdr.Domain.SagaHost/Dockerfile . + - docker build -t $LEANDA_WEBAPI_IMAGE:ci -f Sds.Osdr.WebApi/Dockerfile . + - if [ -d $DOCKER_CACHE_FILE ]; then rm $DOCKER_CACHE_FILE; fi + - docker save -o $DOCKER_CACHE_FILE $LEANDA_PERSISTENCE_IMAGE:ci $LEANDA_FRONTEND_IMAGE:ci $LEANDA_BACKEND_IMAGE:ci $LEANDA_SAGAHOST_IMAGE:ci $LEANDA_WEBAPI_IMAGE:ci + - stage: Integration tests + name: Run Web API integration tests + script: + - docker load -i $DOCKER_CACHE_FILE + - docker build -t $LEANDA_WEBAPI_INTEGRATION_TESTS_IMAGE:ci -f Sds.Osdr.WebApi.IntegrationTests/Dockerfile . + - docker images + - yes | cp -rf Sds.Osdr.WebApi.IntegrationTests/.env.travis-ci Sds.Osdr.WebApi.IntegrationTests/.env + - cd Sds.Osdr.WebApi.IntegrationTests && docker-compose up --abort-on-container-exit + - script: + - docker load -i $DOCKER_CACHE_FILE + - docker build -t leanda/integration:ci -f Sds.Osdr.IntegrationTests/Dockerfile . + - docker images + - yes | cp -rf Sds.Osdr.IntegrationTests/.env.travis-ci Sds.Osdr.IntegrationTests/.env + - cd Sds.Osdr.IntegrationTests && docker-compose up --abort-on-container-exit + name: Run processing integration tests + - stage: Deploy + name: Deploy new images to docker hub + script: + - docker load -i $DOCKER_CACHE_FILE + - docker tag $LEANDA_PERSISTENCE_IMAGE:ci $LEANDA_PERSISTENCE_IMAGE:latest + - docker tag $LEANDA_PERSISTENCE_IMAGE:ci $LEANDA_PERSISTENCE_IMAGE:$DATE + - docker tag $LEANDA_FRONTEND_IMAGE:ci $LEANDA_FRONTEND_IMAGE:latest + - docker tag $LEANDA_FRONTEND_IMAGE:ci $LEANDA_FRONTEND_IMAGE:$DATE + - docker tag $LEANDA_BACKEND_IMAGE:ci $LEANDA_BACKEND_IMAGE:latest + - docker tag $LEANDA_BACKEND_IMAGE:ci $LEANDA_BACKEND_IMAGE:$DATE + - docker tag $LEANDA_SAGAHOST_IMAGE:ci $LEANDA_SAGAHOST_IMAGE:latest + - docker tag $LEANDA_SAGAHOST_IMAGE:ci $LEANDA_SAGAHOST_IMAGE:$DATE + - docker tag $LEANDA_WEBAPI_IMAGE:ci $LEANDA_WEBAPI_IMAGE:latest + - docker tag $LEANDA_WEBAPI_IMAGE:ci $LEANDA_WEBAPI_IMAGE:$DATE + - docker images + - docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" + - docker push $LEANDA_PERSISTENCE_IMAGE:latest + - docker push $LEANDA_PERSISTENCE_IMAGE:$DATE + - docker push $LEANDA_FRONTEND_IMAGE:latest + - docker push $LEANDA_FRONTEND_IMAGE:$DATE + - docker push $LEANDA_BACKEND_IMAGE:latest + - docker push $LEANDA_BACKEND_IMAGE:$DATE + - docker push $LEANDA_SAGAHOST_IMAGE:latest + - docker push $LEANDA_SAGAHOST_IMAGE:$DATE + - docker push $LEANDA_WEBAPI_IMAGE:latest + - docker push $LEANDA_WEBAPI_IMAGE:$DATE + - rm -f $DOCKER_CACHE_FILE diff --git a/Leanda.Categories/BackEnd/CommandHandlers/CategoryTreeCommandHandler.cs b/Leanda.Categories/BackEnd/CommandHandlers/CategoryTreeCommandHandler.cs new file mode 100644 index 0000000..f5c3167 --- /dev/null +++ b/Leanda.Categories/BackEnd/CommandHandlers/CategoryTreeCommandHandler.cs @@ -0,0 +1,61 @@ +using CQRSlite.Domain; +using CQRSlite.Domain.Exception; +using Leanda.Categories.Domain; +using Leanda.Categories.Domain.Commands; +using Leanda.Categories.Domain.Events; +using MassTransit; +using Serilog; +using System; +using System.Threading.Tasks; + +namespace Leanda.Categories.BackEnd.CommandHandlers +{ + public class CategoryTreeCommandHandler : IConsumer, + IConsumer, + IConsumer, + IConsumer + { + private readonly ISession _session; + + public CategoryTreeCommandHandler(ISession session) + { + _session = session ?? throw new ArgumentNullException(nameof(session)); + } + + public async Task Consume(ConsumeContext context) + { + var tree = new CategoryTree(context.Message.Id, userId: context.Message.UserId, nodes: context.Message.Nodes); + + await _session.Add(tree); + + await _session.Commit(); + } + + public async Task Consume(ConsumeContext context) + { + var tree = await _session.Get(context.Message.Id); + + tree.Update(context.Message.UserId, context.Message.ParentId, context.Message.Nodes); + + await _session.Commit(); + } + + public async Task Consume(ConsumeContext context) + { + var tree = await _session.Get(context.Message.Id); + + tree.Delete(context.Message.UserId); + + await _session.Commit(); + } + + public async Task Consume(ConsumeContext context) + { + var tree = await _session.Get(context.Message.Id); + + tree.DeleteNode(context.Message.UserId, context.Message.NodeId); + + await _session.Commit(); + } + } +} diff --git a/Leanda.Categories/Domain/Aggregates/CategoryTree.cs b/Leanda.Categories/Domain/Aggregates/CategoryTree.cs new file mode 100644 index 0000000..d4a85f0 --- /dev/null +++ b/Leanda.Categories/Domain/Aggregates/CategoryTree.cs @@ -0,0 +1,89 @@ +using CQRSlite.Domain; +using Leanda.Categories.Domain.Events; +using Leanda.Categories.Domain.ValueObjects; +using System; +using System.Collections.Generic; + +namespace Leanda.Categories.Domain +{ + public class CategoryTree : AggregateRoot + { + /// + /// User Id of the person who created the file + /// + public Guid CreatedBy { get; private set; } + + /// + /// Date and time when file was created + /// + public DateTimeOffset CreatedDateTime { get; private set; } + + /// + /// User Id of person who changed the file last time + /// + public Guid UpdatedBy { get; protected set; } + + /// + /// Date and time when file was changed last time + /// + public DateTimeOffset UpdatedDateTime { get; protected set; } + + public List Nodes { get; protected set; } + + private void Apply(CategoryTreeCreated e) + { + CreatedBy = e.UserId; + CreatedDateTime = e.TimeStamp; + UpdatedBy = e.UserId; + UpdatedDateTime = e.TimeStamp; + Nodes = e.Nodes; + } + + private void Apply(CategoryTreeUpdated e) + { + UpdatedBy = e.UserId; + UpdatedDateTime = e.TimeStamp; + + if (e.ParentId == null) + { + Nodes = e.Nodes; + } + else + { + Nodes.UpdateCategoryNodeById(e.Id, e.Nodes); + } + } + + private void Apply(CategoryTreeNodeDeleted e) + { + UpdatedBy = e.UserId; + UpdatedDateTime = e.TimeStamp; + Nodes.DeleteCategoryNodeById(e.Id); + } + + protected CategoryTree() + { + } + + public CategoryTree(Guid id, Guid userId, List nodes) + { + Id = id; + ApplyChange(new CategoryTreeCreated(Id, userId, nodes)); + } + + public void Update(Guid userId, Guid? parentId, List nodes) + { + ApplyChange(new CategoryTreeUpdated(Id, userId, parentId, nodes)); + } + + public void DeleteNode(Guid userId, Guid nodeId) + { + ApplyChange(new CategoryTreeNodeDeleted(Id, userId, nodeId)); + } + + public void Delete(Guid userId) + { + ApplyChange(new CategoryTreeDeleted(Id, userId)); + } + } +} diff --git a/Leanda.Categories/Domain/Commands/AddEntityCategories.cs b/Leanda.Categories/Domain/Commands/AddEntityCategories.cs new file mode 100644 index 0000000..2141dba --- /dev/null +++ b/Leanda.Categories/Domain/Commands/AddEntityCategories.cs @@ -0,0 +1,14 @@ +using MassTransit; +using System; +using System.Collections.Generic; + +namespace Leanda.Categories.Domain.Commands +{ + public interface AddEntityCategories : CorrelatedBy + { + Guid Id { get; } + Guid EntityId { get; } + IEnumerable CategoriesIds { get; } + Guid UserId { get; } + } +} diff --git a/Leanda.Categories/Domain/Commands/CreateCategoryTree.cs b/Leanda.Categories/Domain/Commands/CreateCategoryTree.cs new file mode 100644 index 0000000..6c438b1 --- /dev/null +++ b/Leanda.Categories/Domain/Commands/CreateCategoryTree.cs @@ -0,0 +1,14 @@ +using Leanda.Categories.Domain.ValueObjects; +using MassTransit; +using System; +using System.Collections.Generic; + +namespace Leanda.Categories.Domain.Commands +{ + public interface CreateCategoryTree : CorrelatedBy + { + Guid Id { get; } + List Nodes { get; set; } + Guid UserId { get; } + } +} diff --git a/Leanda.Categories/Domain/Commands/DeleteCategoryTree.cs b/Leanda.Categories/Domain/Commands/DeleteCategoryTree.cs new file mode 100644 index 0000000..116d070 --- /dev/null +++ b/Leanda.Categories/Domain/Commands/DeleteCategoryTree.cs @@ -0,0 +1,14 @@ +using Leanda.Categories.Domain.ValueObjects; +using MassTransit; +using System; +using System.Collections.Generic; + +namespace Leanda.Categories.Domain.Commands +{ + public interface DeleteCategoryTree : CorrelatedBy + { + Guid Id { get; } + Guid? NodeId { get; } + Guid UserId { get; } + } +} diff --git a/Leanda.Categories/Domain/Commands/DeleteCategoryTreeNode.cs b/Leanda.Categories/Domain/Commands/DeleteCategoryTreeNode.cs new file mode 100644 index 0000000..0d1e73f --- /dev/null +++ b/Leanda.Categories/Domain/Commands/DeleteCategoryTreeNode.cs @@ -0,0 +1,12 @@ +using MassTransit; +using System; + +namespace Leanda.Categories.Domain.Commands +{ + public interface DeleteCategoryTreeNode : CorrelatedBy + { + Guid Id { get; } + Guid NodeId { get; } + Guid UserId { get; } + } +} diff --git a/Leanda.Categories/Domain/Commands/DeleteEntityCategories.cs b/Leanda.Categories/Domain/Commands/DeleteEntityCategories.cs new file mode 100644 index 0000000..579e7fd --- /dev/null +++ b/Leanda.Categories/Domain/Commands/DeleteEntityCategories.cs @@ -0,0 +1,14 @@ +using MassTransit; +using System; +using System.Collections.Generic; + +namespace Leanda.Categories.Domain.Commands +{ + public interface DeleteEntityCategories : CorrelatedBy + { + Guid Id { get; } + Guid EntityId { get; } + IEnumerable CategoriesIds { get; } + Guid UserId { get; } + } +} diff --git a/Leanda.Categories/Domain/Commands/UpdateCategoryTree.cs b/Leanda.Categories/Domain/Commands/UpdateCategoryTree.cs new file mode 100644 index 0000000..07b185b --- /dev/null +++ b/Leanda.Categories/Domain/Commands/UpdateCategoryTree.cs @@ -0,0 +1,16 @@ +using Leanda.Categories.Domain.ValueObjects; +using MassTransit; +using System; +using System.Collections.Generic; + +namespace Leanda.Categories.Domain.Commands +{ + public interface UpdateCategoryTree : CorrelatedBy + { + Guid Id { get; } + Guid? ParentId { get; set; } + List Nodes { get; set; } + Guid UserId { get; } + int ExpectedVersion { get; } + } +} diff --git a/Leanda.Categories/Domain/Events/CategoryTreeCreated.cs b/Leanda.Categories/Domain/Events/CategoryTreeCreated.cs new file mode 100644 index 0000000..a9a06e7 --- /dev/null +++ b/Leanda.Categories/Domain/Events/CategoryTreeCreated.cs @@ -0,0 +1,24 @@ +using Leanda.Categories.Domain.ValueObjects; +using Sds.CqrsLite.Events; +using System; +using System.Collections.Generic; + +namespace Leanda.Categories.Domain.Events +{ + public class CategoryTreeCreated : IUserEvent + { + public List Nodes { get; set; } + + public CategoryTreeCreated(Guid id, Guid userId, List nodes) + { + Id = id; + Nodes = nodes; + UserId = userId; + } + + public Guid UserId { get; set; } + public Guid Id { get; set; } + public DateTimeOffset TimeStamp { get; set; } = DateTimeOffset.UtcNow; + public int Version { get; set; } + } +} diff --git a/Leanda.Categories/Domain/Events/CategoryTreeDeletePersisted.cs b/Leanda.Categories/Domain/Events/CategoryTreeDeletePersisted.cs new file mode 100644 index 0000000..3aaa650 --- /dev/null +++ b/Leanda.Categories/Domain/Events/CategoryTreeDeletePersisted.cs @@ -0,0 +1,24 @@ +using Leanda.Categories.Domain.ValueObjects; +using Sds.CqrsLite.Events; +using System; +using System.Collections.Generic; + +namespace Leanda.Categories.Domain.Events +{ + public class CategoryTreeDeletePersisted : IUserEvent + { + public Guid? NodeId { get; set; } + + public CategoryTreeDeletePersisted(Guid id, Guid userId, Guid? nodeId) + { + Id = id; + UserId = userId; + NodeId = nodeId; + } + + public Guid UserId { get; set; } + public Guid Id { get; set; } + public DateTimeOffset TimeStamp { get; set; } = DateTimeOffset.UtcNow; + public int Version { get; set; } + } +} diff --git a/Leanda.Categories/Domain/Events/CategoryTreeDeleted.cs b/Leanda.Categories/Domain/Events/CategoryTreeDeleted.cs new file mode 100644 index 0000000..2d56a0c --- /dev/null +++ b/Leanda.Categories/Domain/Events/CategoryTreeDeleted.cs @@ -0,0 +1,21 @@ +using Leanda.Categories.Domain.ValueObjects; +using Sds.CqrsLite.Events; +using System; +using System.Collections.Generic; + +namespace Leanda.Categories.Domain.Events +{ + public class CategoryTreeDeleted : IUserEvent + { + public CategoryTreeDeleted(Guid id, Guid userId) + { + Id = id; + UserId = userId; + } + + public Guid UserId { get; set; } + public Guid Id { get; set; } + public DateTimeOffset TimeStamp { get; set; } = DateTimeOffset.UtcNow; + public int Version { get; set; } + } +} diff --git a/Leanda.Categories/Domain/Events/CategoryTreeNodeDeletePersisted.cs b/Leanda.Categories/Domain/Events/CategoryTreeNodeDeletePersisted.cs new file mode 100644 index 0000000..b101eb4 --- /dev/null +++ b/Leanda.Categories/Domain/Events/CategoryTreeNodeDeletePersisted.cs @@ -0,0 +1,16 @@ +using Leanda.Categories.Domain.ValueObjects; +using Sds.CqrsLite.Events; +using System; +using System.Collections.Generic; + +namespace Leanda.Categories.Domain.Events +{ + public interface CategoryTreeNodeDeletePersisted + { + Guid NodeId { get; } + Guid UserId { get; } + Guid Id { get; } + DateTimeOffset TimeStamp { get; } + int Version { get; } + } +} diff --git a/Leanda.Categories/Domain/Events/CategoryTreeNodeDeleted.cs b/Leanda.Categories/Domain/Events/CategoryTreeNodeDeleted.cs new file mode 100644 index 0000000..870ab97 --- /dev/null +++ b/Leanda.Categories/Domain/Events/CategoryTreeNodeDeleted.cs @@ -0,0 +1,24 @@ +using Leanda.Categories.Domain.ValueObjects; +using Sds.CqrsLite.Events; +using System; +using System.Collections.Generic; + +namespace Leanda.Categories.Domain.Events +{ + public class CategoryTreeNodeDeleted : IUserEvent + { + public Guid NodeId { get; set; } + + public CategoryTreeNodeDeleted(Guid id, Guid userId, Guid nodeId) + { + Id = id; + NodeId = nodeId; + UserId = userId; + } + + public Guid UserId { get; set; } + public Guid Id { get; set; } + public DateTimeOffset TimeStamp { get; set; } = DateTimeOffset.UtcNow; + public int Version { get; set; } + } +} diff --git a/Leanda.Categories/Domain/Events/CategoryTreePersisted.cs b/Leanda.Categories/Domain/Events/CategoryTreePersisted.cs new file mode 100644 index 0000000..01c6077 --- /dev/null +++ b/Leanda.Categories/Domain/Events/CategoryTreePersisted.cs @@ -0,0 +1,8 @@ +using CQRSlite.Events; + +namespace Leanda.Categories.Domain.Events +{ + public interface CategoryTreePersisted : IEvent + { + } +} diff --git a/Leanda.Categories/Domain/Events/CategoryTreeUpdated.cs b/Leanda.Categories/Domain/Events/CategoryTreeUpdated.cs new file mode 100644 index 0000000..2f468ed --- /dev/null +++ b/Leanda.Categories/Domain/Events/CategoryTreeUpdated.cs @@ -0,0 +1,26 @@ +using Leanda.Categories.Domain.ValueObjects; +using Sds.CqrsLite.Events; +using System; +using System.Collections.Generic; + +namespace Leanda.Categories.Domain.Events +{ + public class CategoryTreeUpdated : IUserEvent + { + public Guid? ParentId { get; set; } + public List Nodes { get; set; } + + public CategoryTreeUpdated(Guid id, Guid userId, Guid? parentId, List nodes) + { + Id = id; + Nodes = nodes; + UserId = userId; + ParentId = parentId; + } + + public Guid UserId { get; set; } + public Guid Id { get; set; } + public DateTimeOffset TimeStamp { get; set; } = DateTimeOffset.UtcNow; + public int Version { get; set; } + } +} diff --git a/Leanda.Categories/Domain/Events/CategoryTreeUpdatedpersisted.cs b/Leanda.Categories/Domain/Events/CategoryTreeUpdatedpersisted.cs new file mode 100644 index 0000000..58c5aa9 --- /dev/null +++ b/Leanda.Categories/Domain/Events/CategoryTreeUpdatedpersisted.cs @@ -0,0 +1,26 @@ +using Leanda.Categories.Domain.ValueObjects; +using Sds.CqrsLite.Events; +using System; +using System.Collections.Generic; + +namespace Leanda.Categories.Domain.Events +{ + public class CategoryTreeUpdatedPersisted : IUserEvent + { + public Guid? ParentId { get; set; } + public List Nodes { get; set; } + + public CategoryTreeUpdatedPersisted(Guid id, Guid userId, Guid? parentId, List nodes) + { + Id = id; + Nodes = nodes; + UserId = userId; + ParentId = parentId; + } + + public Guid UserId { get; set; } + public Guid Id { get; set; } + public DateTimeOffset TimeStamp { get; set; } = DateTimeOffset.UtcNow; + public int Version { get; set; } + } +} diff --git a/Leanda.Categories/Domain/Events/IEntityCategoriesAddPersisted.cs b/Leanda.Categories/Domain/Events/IEntityCategoriesAddPersisted.cs new file mode 100644 index 0000000..854f9fd --- /dev/null +++ b/Leanda.Categories/Domain/Events/IEntityCategoriesAddPersisted.cs @@ -0,0 +1,8 @@ +using CQRSlite.Events; + +namespace Leanda.Categories.Domain.Events +{ + public interface IEntityCategoriesAddPersisted + { + } +} diff --git a/Leanda.Categories/Domain/Events/UnexpectedVersion.cs b/Leanda.Categories/Domain/Events/UnexpectedVersion.cs new file mode 100644 index 0000000..69d761e --- /dev/null +++ b/Leanda.Categories/Domain/Events/UnexpectedVersion.cs @@ -0,0 +1,12 @@ +using System; + +namespace Leanda.Categories.Domain.Events +{ + public interface UnexpectedVersion + { + Guid Id { get; } + Guid UserId { get; } + int Version { get; } + DateTimeOffset TimeStamp { get; } + } +} diff --git a/Leanda.Categories/Domain/ValueObjects/TreeNode.cs b/Leanda.Categories/Domain/ValueObjects/TreeNode.cs new file mode 100644 index 0000000..cb1017d --- /dev/null +++ b/Leanda.Categories/Domain/ValueObjects/TreeNode.cs @@ -0,0 +1,124 @@ +using Newtonsoft.Json; +using Sds.Domain; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Leanda.Categories.Domain.ValueObjects +{ + public class TreeNode : ValueObject + { + public Guid Id { get; set; } + public string Title { get; set; } + public List Children { get; set; } + + public TreeNode(Guid id, string title, List children = null) + { + Id = id; + Title = title; + Children = children; + } + + public TreeNode() + { + } + + public TreeNode(string title, List children = null) + { + Title = title; + Children = children; + } + + protected override IEnumerable GetAttributesToIncludeInEqualityCheck() + { + return new List() { Id, Title }; + } + } + + public static class EnumerableTreeNodeExtensions + { + public static void InitNodeIds(this IEnumerable nodes) + { + foreach (var node in nodes) + { + if (node.Id == default(Guid)) + { + node.Id = Guid.NewGuid(); + } + + if (node.Children != null && node.Children.Any()) + { + node.Children.InitNodeIds(); + } + } + } + + public static IEnumerable GetNodeIds(this IEnumerable nodes) + { + foreach (var node in nodes) + { + if (node.Id != default(Guid)) + { + yield return node.Id; + } + + if (node.Children != null && node.Children.Any()) + { + var guids = node.Children.GetNodeIds(); + foreach (var id in guids) + { + yield return id; + } + } + } + } + + public static bool ContainsTree(this IEnumerable nodes, Guid id) + { + foreach (var node in nodes) + { + if (node.Id == id) + { + return true; + } + if (node.Children != null) + { + return node.Children.ContainsTree(id); + } + } + return false; + } + + public static void DeleteCategoryNodeById(this IEnumerable nodes, Guid id) + { + foreach (var node in nodes) + { + if (node.Id == id) + { + nodes.ToList().Remove(nodes.Single(i => i.Id == id)); + return; + } + if (node.Children != null) + { + node.Children.DeleteCategoryNodeById(id); + } + } + } + + public static void UpdateCategoryNodeById(this IEnumerable nodes, Guid id, IEnumerable newNodes) + { + foreach (var node in nodes) + { + if (node.Id == id) + { + node.Children = newNodes.ToList(); + return; + } + if (node.Children != null) + { + node.Children.DeleteCategoryNodeById(id); + } + } + } + } +} diff --git a/Leanda.Categories/Leanda.CategoryTree.csproj b/Leanda.Categories/Leanda.CategoryTree.csproj new file mode 100644 index 0000000..bfc23e2 --- /dev/null +++ b/Leanda.Categories/Leanda.CategoryTree.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + + + + diff --git a/Leanda.Categories/Modules/CategoryTreeModule.cs b/Leanda.Categories/Modules/CategoryTreeModule.cs new file mode 100644 index 0000000..5d927fa --- /dev/null +++ b/Leanda.Categories/Modules/CategoryTreeModule.cs @@ -0,0 +1,59 @@ +using MassTransit; +using MassTransit.RabbitMqTransport; +using Microsoft.Extensions.DependencyInjection; +using Sds.CqrsLite.MassTransit.Filters; +using Sds.MassTransit.Extensions; +using Sds.MassTransit.RabbitMq; +using Sds.MassTransit.Saga; +using System; + +namespace Leanda.Categories.Modules +{ + public static class ServiceCollectionExtensions + { + public static void UseInMemoryModule(this IServiceCollection services) + { + // add backend consumers... + services.AddScoped(); + + // add persistence consumers... + services.AddTransient(); + } + + public static void UseBackEndModule(this IServiceCollection services) + { + // add backend consumers... + services.AddScoped(); + } + + public static void UsePersistenceModule(this IServiceCollection services) + { + // add persistence consumers... + services.AddTransient(); + } + } + + public static class ConfigurationExtensions + { + public static void RegisterInMemoryModule(this IBusFactoryConfigurator configurator, IServiceProvider provider) + { + // register backend consumers... + configurator.RegisterScopedConsumer(provider, null, c => c.UseCqrsLite()); + + // register persistence consumers... + configurator.RegisterConsumer(provider); + } + + public static void RegisterBackEndModule(this IRabbitMqBusFactoryConfigurator configurator, IRabbitMqHost host, IServiceProvider provider, Action endpointConfigurator = null) + { + // register backend consumers... + configurator.RegisterScopedConsumer(host, provider, endpointConfigurator, c => c.UseCqrsLite()); + } + + public static void RegisterPersistenceModule(this IRabbitMqBusFactoryConfigurator configurator, IRabbitMqHost host, IServiceProvider provider, Action endpointConfigurator = null) + { + // register persistence consumers... + configurator.RegisterConsumer(host, provider, endpointConfigurator); + } + } +} diff --git a/Leanda.Categories/Persistence/EventHandlers/CategoryTreeEventHandlers.cs b/Leanda.Categories/Persistence/EventHandlers/CategoryTreeEventHandlers.cs new file mode 100644 index 0000000..0d08b7d --- /dev/null +++ b/Leanda.Categories/Persistence/EventHandlers/CategoryTreeEventHandlers.cs @@ -0,0 +1,138 @@ +using CQRSlite.Domain.Exception; +using Leanda.Categories.Domain.Events; +using Leanda.Categories.Domain.ValueObjects; +using MassTransit; +using MongoDB.Bson; +using MongoDB.Bson.Serialization; +using MongoDB.Driver; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Threading.Tasks; + +namespace Leanda.Categories.Persistence.EventHandlers +{ + public class CategoryTreeEventHandlers : IConsumer, + IConsumer, + IConsumer, + IConsumer + { + private readonly IMongoDatabase _database; + private readonly IMongoCollection _categoryTreeCollection; + + public CategoryTreeEventHandlers(IMongoDatabase database) + { + _database = database ?? throw new ArgumentNullException(nameof(database)); + _categoryTreeCollection = _database.GetCollection("CategoryTrees"); + } + + public async Task Consume(ConsumeContext context) + { + var tree = new + { + CreatedBy = context.Message.UserId, + CreatedDateTime = context.Message.TimeStamp.UtcDateTime, + UpdatedBy = context.Message.UserId, + UpdatedDateTime = context.Message.TimeStamp.UtcDateTime, + context.Message.Id, + context.Message.Version, + context.Message.Nodes + }.ToBsonDocument(); + + await _categoryTreeCollection.InsertOneAsync(tree); + + await context.Publish(new + { + context.Message.Id, + TimeStamp = DateTimeOffset.UtcNow, + context.Message.Version + }); + } + + public async Task Consume(ConsumeContext context) + { + var filter = new BsonDocument("_id", context.Message.Id).Add("Version", context.Message.Version - 1); + + var update = Builders.Update + .Set("Nodes", context.Message.Nodes) + .Set("UpdatedBy", context.Message.UserId) + .Set("UpdatedDateTime", context.Message.TimeStamp.UtcDateTime) + .Set("Version", context.Message.Version); + + var element = await _categoryTreeCollection.FindOneAndUpdateAsync(filter, update); + + if (element == null) + throw new ConcurrencyException(context.Message.Id); + + await context.Publish(new + { + context.Message.Id, + TimeStamp = DateTimeOffset.UtcNow, + context.Message.Version + }); + } + + public async Task Consume(ConsumeContext context) + { + var filter = new BsonDocument("_id", context.Message.Id).Add("Version", context.Message.Version - 1); + + var element = await _categoryTreeCollection.FindOneAndDeleteAsync(filter); + if (element == null) + throw new ConcurrencyException(context.Message.Id); + + await context.Publish(new + { + context.Message.Id, + TimeStamp = DateTimeOffset.UtcNow, + context.Message.Version + }); + } + + public async Task Consume(ConsumeContext context) + { + var filter = new BsonDocument("_id", context.Message.Id).Add("Version", context.Message.Version - 1); + + var treeBson =_categoryTreeCollection.Find(filter).Project(@"{ + Nodes:1 + }").Single(); + var nodes = ((IDictionary)treeBson)["Nodes"]; + + nodes = RemoveNodeById(nodes, context.Message.NodeId); + + var update = Builders.Update + .Set("Nodes", nodes) + .Set("Version", context.Message.Version); + + var element = await _categoryTreeCollection.FindOneAndUpdateAsync(filter, update); + + await context.Publish(new + { + context.Message.Id, + context.Message.NodeId, + TimeStamp = DateTimeOffset.UtcNow, + context.Message.Version + }); + } + + public dynamic RemoveNodeById(dynamic nodes, Guid id) + { + foreach (var node in (nodes as List).ToArray()) + { + if (node._id == id) + { + nodes = (nodes as List).Except(new List { node }).ToList(); + return nodes; + } + if (node.Children != null) + { + node.Children = RemoveNodeById(node.Children, id); + } + } + return nodes; + } + + } +} diff --git a/Leanda.Microscopy/BackEnd/CommandHandlers/UpdateBioMetadataCommandHandler.cs b/Leanda.Microscopy/BackEnd/CommandHandlers/UpdateBioMetadataCommandHandler.cs new file mode 100644 index 0000000..9522747 --- /dev/null +++ b/Leanda.Microscopy/BackEnd/CommandHandlers/UpdateBioMetadataCommandHandler.cs @@ -0,0 +1,28 @@ +using CQRSlite.Domain; +using Leanda.Microscopy.Domain; +using Leanda.Microscopy.Domain.Commands; +using MassTransit; +using System; +using System.Threading.Tasks; + +namespace Leanda.Microscopy.BackEnd.CommandHandlers +{ + public class UpdateBioMetadataCommandHandler : IConsumer + { + private readonly ISession session; + + public UpdateBioMetadataCommandHandler(ISession session) + { + this.session = session ?? throw new ArgumentNullException(nameof(session)); + } + + public async Task Consume(ConsumeContext context) + { + var file = await session.Get(context.Message.Id); + + file.UpdateBioMetadata(context.Message.UserId, context.Message.Metadata); + + await session.Commit(); + } + } +} diff --git a/Leanda.Microscopy/Domain/Aggregates/MicroscopyFile.cs b/Leanda.Microscopy/Domain/Aggregates/MicroscopyFile.cs new file mode 100644 index 0000000..aae195d --- /dev/null +++ b/Leanda.Microscopy/Domain/Aggregates/MicroscopyFile.cs @@ -0,0 +1,41 @@ +using Sds.Osdr.Generic.Domain; +using Leanda.Microscopy.Domain.Events; +using System; +using System.Collections.Generic; +using Sds.Osdr.Domain; + +namespace Leanda.Microscopy.Domain +{ + public class MicroscopyFile : File + { + public IList> BioMetadata { get; protected set; } = new List>(); + + private void Apply(MicroscopyFileCreated e) + { + } + + private void Apply(BioMetadataUpdated e) + { + BioMetadata = e.Metadata; + + UpdatedBy = e.UserId; + UpdatedDateTime = e.TimeStamp; + } + + protected MicroscopyFile() + { + } + + public MicroscopyFile(Guid id, Guid userId, Guid? parentId, string fileName, FileStatus fileStatus, string bucket, Guid blobId, long length, string md5) + : base(id, userId, parentId, fileName, fileStatus, bucket, blobId, length, md5, FileType.Microscopy) + { + Id = id; + ApplyChange(new MicroscopyFileCreated(Id)); + } + + public void UpdateBioMetadata(Guid userId, IList> metadata) + { + ApplyChange(new BioMetadataUpdated(Id, userId, metadata)); + } + } +} diff --git a/Leanda.Microscopy/Domain/Commands/CreateMicroscopyFile.cs b/Leanda.Microscopy/Domain/Commands/CreateMicroscopyFile.cs new file mode 100644 index 0000000..d8c0d65 --- /dev/null +++ b/Leanda.Microscopy/Domain/Commands/CreateMicroscopyFile.cs @@ -0,0 +1,17 @@ +using Sds.Domain; +using System; +using System.Collections.Generic; + +namespace Leanda.Microscopy.Domain.Commands +{ + public interface CreateMicroscopyFile + { + Guid Id { get; set; } + Guid FileId { get; } + string Bucket { get; } + Guid BlobId { get; } + long Index { get; } + IEnumerable Fields { get; } + Guid UserId { get; set; } + } +} diff --git a/Leanda.Microscopy/Domain/Commands/UpdateBioMetadata.cs b/Leanda.Microscopy/Domain/Commands/UpdateBioMetadata.cs new file mode 100644 index 0000000..d522ee0 --- /dev/null +++ b/Leanda.Microscopy/Domain/Commands/UpdateBioMetadata.cs @@ -0,0 +1,14 @@ +using Sds.Osdr.Domain; +using System; +using System.Collections.Generic; + +namespace Leanda.Microscopy.Domain.Commands +{ + public interface UpdateBioMetadata + { + IList> Metadata { get; } + Guid Id { get; } + Guid UserId { get; } + int ExpectedVersion { get; } + } +} diff --git a/Leanda.Microscopy/Domain/Events/BioMetadataPersisted.cs b/Leanda.Microscopy/Domain/Events/BioMetadataPersisted.cs new file mode 100644 index 0000000..866ea31 --- /dev/null +++ b/Leanda.Microscopy/Domain/Events/BioMetadataPersisted.cs @@ -0,0 +1,11 @@ +using System; + +namespace Leanda.Microscopy.Domain.Events +{ + public interface BioMetadataPersisted + { + Guid Id { get; } + Guid UserId { get; } + DateTimeOffset TimeStamp { get; } + } +} diff --git a/Leanda.Microscopy/Domain/Events/BioMetadataUpdated.cs b/Leanda.Microscopy/Domain/Events/BioMetadataUpdated.cs new file mode 100644 index 0000000..351a16f --- /dev/null +++ b/Leanda.Microscopy/Domain/Events/BioMetadataUpdated.cs @@ -0,0 +1,27 @@ +using Sds.CqrsLite.Events; +using Sds.Osdr.Domain; +using System; +using System.Collections.Generic; + +namespace Leanda.Microscopy.Domain.Events +{ + public class BioMetadataUpdated : IUserEvent + { + public readonly IList> Metadata; + + public BioMetadataUpdated(Guid id, Guid userId, IList> metadata) + { + Id = id; + UserId = userId; + Metadata = metadata; + } + + public Guid Id { get; set; } + + public Guid UserId { get; set; } + + public DateTimeOffset TimeStamp { get; set; } = DateTimeOffset.UtcNow; + + public int Version { get; set; } + } +} diff --git a/Leanda.Microscopy/Domain/Events/MicroscopyFileCreated.cs b/Leanda.Microscopy/Domain/Events/MicroscopyFileCreated.cs new file mode 100644 index 0000000..1f83cf8 --- /dev/null +++ b/Leanda.Microscopy/Domain/Events/MicroscopyFileCreated.cs @@ -0,0 +1,21 @@ +using Sds.CqrsLite.Events; +using System; + +namespace Leanda.Microscopy.Domain.Events +{ + public class MicroscopyFileCreated : IUserEvent + { + public MicroscopyFileCreated(Guid id) + { + Id = id; + } + + public Guid Id { get; set; } + + public Guid UserId { get; set; } + + public DateTimeOffset TimeStamp { get; set; } = DateTimeOffset.UtcNow; + + public int Version { get; set; } + } +} diff --git a/Leanda.Microscopy/Leanda.Microscopy.csproj b/Leanda.Microscopy/Leanda.Microscopy.csproj new file mode 100644 index 0000000..5874c4f --- /dev/null +++ b/Leanda.Microscopy/Leanda.Microscopy.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.0 + + + + + + + + + + + diff --git a/Leanda.Microscopy/Modules/MicroscopyModule.cs b/Leanda.Microscopy/Modules/MicroscopyModule.cs new file mode 100644 index 0000000..63ac21a --- /dev/null +++ b/Leanda.Microscopy/Modules/MicroscopyModule.cs @@ -0,0 +1,141 @@ +using CQRSlite.Domain; +using Leanda.Microscopy.Domain; +using Leanda.Microscopy.Sagas; +using Leanda.Microscopy.Sagas.Commands; +using MassTransit; +using MassTransit.RabbitMqTransport; +using MassTransit.Saga; +using Microsoft.Extensions.DependencyInjection; +using Sds.CqrsLite.MassTransit.Filters; +using Sds.MassTransit.Extensions; +using Sds.MassTransit.RabbitMq; +using Sds.MassTransit.Saga; +using Sds.Osdr.Domain.Modules; +using Sds.Osdr.Generic.Domain; +using Sds.Osdr.Infrastructure.Extensions; +using Sds.Storage.Blob.Events; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Leanda.Microscopy.Modules +{ + public class MicroscopyModule : IModule + { + private readonly ISession _session; + private readonly IBusControl _bus; + + public MicroscopyModule(ISession session, IBusControl bus) + { + _session = session ?? throw new ArgumentNullException(nameof(session)); + _bus = bus ?? throw new ArgumentNullException(nameof(bus)); + } + + public bool IsSupported(BlobLoaded blob) + { + return (new string[] { ".ims", ".czi", ".tif", ".lif", ".nd2", ".lsm" }).Contains(Path.GetExtension(blob.BlobInfo.FileName).ToLower()); + } + + public async Task Process(BlobLoaded blob) + { + var fileId = NewId.NextGuid(); + var blobInfo = blob.BlobInfo; + Guid userId = blobInfo.UserId.HasValue ? blobInfo.UserId.Value : new Guid(blobInfo.Metadata[nameof(userId)].ToString()); + //TODO: is it possible that parentId is nul??? + Guid? parentId = blobInfo.Metadata != null ? blobInfo.Metadata.ContainsKey(nameof(parentId)) ? (Guid?)new Guid(blobInfo.Metadata[nameof(parentId)].ToString()) : null : null; + + var file = new MicroscopyFile(fileId, userId, parentId, blobInfo.FileName, FileStatus.Loaded, blobInfo.Bucket, blobInfo.Id, blobInfo.Length, blobInfo.MD5); + await _session.Add(file); + await _session.Commit(); + + await _bus.Publish(new + { + Id = fileId, + Bucket = blobInfo.Bucket, + ParentId = parentId, + BlobId = blobInfo.Id, + UserId = userId + }); + } + } + + public static class ServiceCollectionExtensions + { + public static void UseInMemoryModule(this IServiceCollection services) + { + services.AddScoped(); + + // add backend consumers... + services.AddScoped(); + + // add persistence consumers... + services.AddTransient(); + services.AddTransient(); + + // add state machines... + services.AddSingleton(); + + // add state machines repositories... + services.AddSingleton>(new InMemorySagaRepository()); + } + + public static void UseBackEndModule(this IServiceCollection services) + { + services.AddScoped(); + + // add backend consumers... + services.AddScoped(); + } + + public static void UsePersistenceModule(this IServiceCollection services) + { + services.AddTransient(); + + // add persistence consumers... + services.AddTransient(); + services.AddTransient(); + } + + public static void UseSagaHostModule(this IServiceCollection services) + { + services.AddTransient(); + + // add state machines... + services.AddSingleton(); + } + } + + public static class ConfigurationExtensions + { + public static void RegisterInMemoryModule(this IBusFactoryConfigurator configurator, IServiceProvider provider) + { + configurator.RegisterConsumer(provider); + configurator.RegisterConsumer(provider); + + // register state machines... + configurator.RegisterStateMachine(provider); + } + + public static void RegisterBackEndModule(this IRabbitMqBusFactoryConfigurator bus, IRabbitMqHost host, IServiceProvider provider, Action endpointConfigurator = null) + { + // register backend consumers... + bus.RegisterScopedConsumer(host, provider, endpointConfigurator, c => c.UseCqrsLite()); + } + + public static void RegisterPersistenceModule(this IRabbitMqBusFactoryConfigurator configurator, IRabbitMqHost host, IServiceProvider provider, Action endpointConfigurator = null) + { + // register persistence consumers... + configurator.RegisterConsumer(host, provider, endpointConfigurator); + configurator.RegisterConsumer(host, provider, endpointConfigurator); + } + + public static void RegisterSagaHostModule(this IRabbitMqBusFactoryConfigurator configurator, IRabbitMqHost host, IServiceProvider provider, Action endpointConfigurator = null) + { + var repositoryFactory = provider.GetRequiredService(); + + // register state machines... + configurator.RegisterStateMachine(host, provider, repositoryFactory, endpointConfigurator); + } + } +} diff --git a/Leanda.Microscopy/Persistence/EventHandlers/FilesEventHandlers.cs b/Leanda.Microscopy/Persistence/EventHandlers/FilesEventHandlers.cs new file mode 100644 index 0000000..ee34707 --- /dev/null +++ b/Leanda.Microscopy/Persistence/EventHandlers/FilesEventHandlers.cs @@ -0,0 +1,67 @@ +using CQRSlite.Domain.Exception; +using Leanda.Microscopy.Domain.Events; +using MassTransit; +using MongoDB.Bson; +using MongoDB.Driver; +using Sds.Osdr.Domain; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Leanda.Microscopy.Persistence.EventHandlers +{ + public class FilesEventHandlers : IConsumer, + IConsumer + { + private readonly IMongoDatabase database; + + private IMongoCollection Files { get { return database.GetCollection("Files"); } } + + public FilesEventHandlers(IMongoDatabase database) + { + this.database = database ?? throw new ArgumentNullException(nameof(database)); + } + + public async Task Consume(ConsumeContext context) + { + var filter = new BsonDocument("_id", context.Message.Id).Add("Version", context.Message.Version - 1); + var update = Builders.Update + .Set("UpdatedBy", context.Message.UserId) + .Set("UpdatedDateTime", context.Message.TimeStamp.UtcDateTime) + .Set("Version", context.Message.Version); + + var document = await Files.FindOneAndUpdateAsync(filter, update); + + if (document == null) + { + throw new ConcurrencyException(context.Message.Id); + } + } + + public async Task Consume(ConsumeContext context) + { + var filter = new BsonDocument("_id", context.Message.Id).Add("Version", context.Message.Version - 1); + var update = Builders.Update + //.Set("Properties", (new { BioMetadata = context.Message.Metadata.Select(m => new { Name = m.Key, Value = m.Value }) }).ToBsonDocument()) + .Set("Properties", (new { BioMetadata = context.Message.Metadata }).ToBsonDocument()) + .Set("UpdatedBy", context.Message.UserId) + .Set("UpdatedDateTime", context.Message.TimeStamp.UtcDateTime) + .Set("Version", context.Message.Version); + + var document = await Files.FindOneAndUpdateAsync(filter, update); + + if (document == null) + { + throw new ConcurrencyException(context.Message.Id); + } + + await context.Publish(new + { + Id = context.Message.Id, + UserId = context.Message.UserId, + TimeStamp = DateTimeOffset.UtcNow + }); + } + } +} diff --git a/Leanda.Microscopy/Persistence/EventHandlers/NodesEventHandlers.cs b/Leanda.Microscopy/Persistence/EventHandlers/NodesEventHandlers.cs new file mode 100644 index 0000000..4a1c879 --- /dev/null +++ b/Leanda.Microscopy/Persistence/EventHandlers/NodesEventHandlers.cs @@ -0,0 +1,51 @@ +using CQRSlite.Domain.Exception; +using MassTransit; +using MongoDB.Bson; +using MongoDB.Driver; +using Leanda.Microscopy.Domain.Events; +using System; +using System.Threading.Tasks; + +namespace Leanda.Microscopy.Persistence.EventHandlers +{ + public class NodesEventHandlers : IConsumer, + IConsumer + { + protected readonly IMongoDatabase _database; + + protected IMongoCollection Nodes { get { return _database.GetCollection("Nodes"); } } + + public NodesEventHandlers(IMongoDatabase database) + { + _database = database ?? throw new ArgumentNullException(nameof(database)); + } + + public async Task Consume(ConsumeContext context) + { + var filter = new BsonDocument("_id", context.Message.Id).Add("Version", context.Message.Version - 1); + var update = Builders.Update + .Set("UpdatedBy", context.Message.UserId) + .Set("UpdatedDateTime", context.Message.TimeStamp.UtcDateTime) + .Set("Version", context.Message.Version); + + var node = await Nodes.FindOneAndUpdateAsync(filter, update); + + if (node == null) + throw new ConcurrencyException(context.Message.Id); + } + + public async Task Consume(ConsumeContext context) + { + var filter = new BsonDocument("_id", context.Message.Id).Add("Version", context.Message.Version - 1); + var update = Builders.Update + .Set("UpdatedBy", context.Message.UserId) + .Set("UpdatedDateTime", context.Message.TimeStamp.UtcDateTime) + .Set("Version", context.Message.Version); + + var document = await Nodes.FindOneAndUpdateAsync(filter, update); + + if (document == null) + throw new ConcurrencyException(context.Message.Id); + } + } +} diff --git a/Leanda.Microscopy/Sagas/Commands/ProcessMicroscopyFile.cs b/Leanda.Microscopy/Sagas/Commands/ProcessMicroscopyFile.cs new file mode 100644 index 0000000..b307e0d --- /dev/null +++ b/Leanda.Microscopy/Sagas/Commands/ProcessMicroscopyFile.cs @@ -0,0 +1,13 @@ +using System; + +namespace Leanda.Microscopy.Sagas.Commands +{ + public interface ProcessMicroscopyFile + { + Guid Id { get; } + Guid ParentId { get; } + string Bucket { get; } + Guid BlobId { get; } + Guid UserId { get; } + } +} diff --git a/Leanda.Microscopy/Sagas/Events/MycroscopyFileProcessed.cs b/Leanda.Microscopy/Sagas/Events/MycroscopyFileProcessed.cs new file mode 100644 index 0000000..269f56c --- /dev/null +++ b/Leanda.Microscopy/Sagas/Events/MycroscopyFileProcessed.cs @@ -0,0 +1,15 @@ +using MassTransit; +using System; + +namespace Leanda.Microscopy.Sagas.Events +{ + public interface MycroscopyFileProcessed : CorrelatedBy + { + Guid Id { get; } + Guid BlobId { get; } + string Bucket { get; } + long ProcessedRecords { get; } + long FailedRecords { get; } + DateTimeOffset TimeStamp { get; } + } +} diff --git a/Leanda.Microscopy/Sagas/MicroscopyFileProcessingStateMachine.cs b/Leanda.Microscopy/Sagas/MicroscopyFileProcessingStateMachine.cs new file mode 100644 index 0000000..9846d0c --- /dev/null +++ b/Leanda.Microscopy/Sagas/MicroscopyFileProcessingStateMachine.cs @@ -0,0 +1,281 @@ +using Automatonymous; +using Leanda.Microscopy.Domain.Commands; +using Leanda.Microscopy.Domain.Events; +using Leanda.Microscopy.Metadata.Domain.Commands; +using Leanda.Microscopy.Metadata.Domain.Events; +using Leanda.Microscopy.Sagas.Commands; +using MassTransit; +using MassTransit.MongoDbIntegration.Saga; +using Sds.Imaging.Domain.Commands; +using Sds.Imaging.Domain.Events; +using Sds.Osdr.Domain; +using Sds.Osdr.Generic.Domain; +using Sds.Osdr.Generic.Domain.Commands.Files; +using Sds.Osdr.Generic.Domain.Events.Files; +using Sds.Osdr.Generic.Domain.ValueObjects; +using Sds.Osdr.Generic.Sagas.Events; +using Serilog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NodeStatusPersisted = Sds.Osdr.Generic.Domain.Events.Files.NodeStatusPersisted; +using StatusPersisted = Sds.Osdr.Generic.Domain.Events.Files.StatusPersisted; + +namespace Leanda.Microscopy.Sagas +{ + public class MicroscopyFileProcessingState : SagaStateMachineInstance, IVersionedSaga + { + public Guid FileId { get; set; } + public Guid ParentId { get; set; } + public Guid UserId { get; set; } + public Guid BlobId { get; set; } + public string Bucket { get; set; } + public string CurrentState { get; set; } + public Guid CorrelationId { get; set; } + public int Version { get; set; } + public Guid _id { get; set; } + public DateTimeOffset Created { get; set; } + public DateTimeOffset Updated { get; set; } + public IList Images { get; set; } = new List(); + public int AllPersisted { get; set; } + public int EndProcessing { get; set; } + } + + public static partial class PublishEndpointExtensions + { + public static async Task GenerateImage(this IPublishEndpoint endpoint, MicroscopyFileProcessingState state, int width, int height) + { + await endpoint.Publish(new + { + Id = state.FileId, + UserId = state.UserId, + BlobId = state.BlobId, + Bucket = state.Bucket, + CorrelationId = state.CorrelationId, + Image = new Sds.Imaging.Domain.Models.Image() + { + Id = NewId.NextGuid(), + Width = width, + Height = height, + Format = "PNG", + MimeType = "image/png" + } + }); + } + } + + public class MicroscopyFileProcessingStateMachine : MassTransitStateMachine + { + public MicroscopyFileProcessingStateMachine() + { + InstanceState(x => x.CurrentState); + + Event(() => ProcessFile, x => x.CorrelateById(context => context.Message.Id).SelectId(context => context.Message.Id)); + Event(() => ImageGenerated, x => x.CorrelateById(context => context.Message.CorrelationId)); + Event(() => ImageGenerationFailed, x => x.CorrelateById(context => context.Message.CorrelationId)); + Event(() => ImageAdded, x => x.CorrelateById(context => context.Message.Id)); + Event(() => MetadataExtracted, x => x.CorrelateById(context => context.Message.CorrelationId)); + Event(() => MetadataExtractionFailed, x => x.CorrelateById(context => context.Message.CorrelationId)); + Event(() => MetadataPersisted, x => x.CorrelateById(context => context.Message.Id)); + Event(() => FileProcessed, x => x.CorrelateById(context => context.Message.CorrelationId)); + Event(() => StatusChanged, x => x.CorrelateById(context => context.Message.Id)); + Event(() => NodeStatusPersisted, x => x.CorrelateById(context => context.Message.Id)); + Event(() => StatusPersisted, x => x.CorrelateById(context => context.Message.Id)); + + CompositeEvent(() => AllPersisted, x => x.AllPersisted, StatusChanged, StatusPersistenceDone, NodeStatusPersistenceDone); + CompositeEvent(() => EndProcessing, x => x.EndProcessing, MetadataExtractionFinished, ImageGenerationFinished); + + Initially( + When(ProcessFile) + .TransitionTo(Processing) + .ThenAsync(async context => + { + Log.Debug($"MicroscopyFile: ProcessMicroscopyFile {context.Data.Id}"); + + context.Instance.Created = DateTimeOffset.UtcNow; + context.Instance.Updated = DateTimeOffset.UtcNow; + context.Instance.FileId = context.Data.Id; + context.Instance.ParentId = context.Data.ParentId; + context.Instance.UserId = context.Data.UserId; + context.Instance.BlobId = context.Data.BlobId; + context.Instance.Bucket = context.Data.Bucket; + + await context.Raise(BeginProcessing); + }) + ); + + During(Processing, + Ignore(NodeStatusPersisted), + Ignore(StatusPersisted), + When(BeginProcessing) + .ThenAsync(async context => + { + await context.CreateConsumeContext().Publish(new + { + Id = context.Instance.FileId, + Status = FileStatus.Processing, + UserId = context.Instance.UserId + }); + }), + When(StatusChanged) + .ThenAsync(async context => + { + await context.CreateConsumeContext().GenerateImage(context.Instance, 300, 300); + await context.CreateConsumeContext().GenerateImage(context.Instance, 600, 600); + await context.CreateConsumeContext().GenerateImage(context.Instance, 1200, 1200); + + await context.CreateConsumeContext().Publish(new + { + Id = context.Instance.FileId, + UserId = context.Instance.UserId, + BlobId = context.Instance.BlobId, + Bucket = context.Instance.Bucket, + CorrelationId = context.Instance.CorrelationId + }); + }), + When(ImageGenerated) + .ThenAsync(async context => { + if (context.Data.TimeStamp > context.Instance.Updated) + context.Instance.Updated = context.Data.TimeStamp; + + await context.CreateConsumeContext().Publish(new + { + Id = context.Instance.FileId, + UserId = context.Instance.UserId, + Image = new Image(context.Instance.Bucket, context.Data.Image.Id, context.Data.Image.Format, context.Data.Image.MimeType, context.Data.Image.Width, context.Data.Image.Height, context.Data.Image.Exception) + }); + }), + When(ImageGenerationFailed) + .ThenAsync(async context => { + if (context.Data.TimeStamp > context.Instance.Updated) + context.Instance.Updated = context.Data.TimeStamp; + + context.Instance.Images.Add(context.Data.Image.Id); + + if (context.Instance.Images.Count == 3) + { + await context.Raise(ImageGenerationFinished); + } + }), + When(ImageAdded) + .ThenAsync(async context => + { + context.Instance.Images.Add(context.Data.Image.Id); + + if (context.Instance.Images.Count == 3) + { + await context.Raise(ImageGenerationFinished); + } + }), + When(MetadataExtracted) + .ThenAsync(async context => { + if (context.Data.TimeStamp > context.Instance.Updated) + context.Instance.Updated = context.Data.TimeStamp; + + await context.CreateConsumeContext().Publish(new + { + Id = context.Instance.FileId, + UserId = context.Instance.UserId, + Metadata = context.Data.Metadata.Select(i => new KeyValue { Name = i.Key, Value = i.Value.ToString()}) + }); + }), + When(MetadataExtractionFailed) + .ThenAsync(async context => { + if (context.Data.TimeStamp > context.Instance.Updated) + context.Instance.Updated = context.Data.TimeStamp; + + await context.Raise(MetadataExtractionFinished); + }), + When(MetadataPersisted) + .ThenAsync(async context => + { + await context.Raise(MetadataExtractionFinished); + }), + When(EndProcessing) + .TransitionTo(Processed) + .ThenAsync(async context => + { + await context.Raise(BeginProcessed); + }) + ); + + During(Processed, + When(BeginProcessed) + .ThenAsync(async context => + { + await context.CreateConsumeContext().Publish(new + { + Id = context.Instance.FileId, + UserId = context.Instance.UserId, + Status = FileStatus.Processed + }); + }), + When(NodeStatusPersisted) + .ThenAsync(async context => + { + if (context.Data.Status != FileStatus.Processing) + { + await context.Raise(NodeStatusPersistenceDone); + } + }), + When(StatusPersisted) + .ThenAsync(async context => + { + if (context.Data.Status != FileStatus.Processing) + { + await context.Raise(StatusPersistenceDone); + } + }), + When(AllPersisted) + .ThenAsync(async context => + { + await context.Raise(EndProcessed); + }), + When(EndProcessed) + .ThenAsync(async context => + { + Log.Debug($"GenericFile: EndProcessed {context.Instance.FileId}"); + + await context.CreateConsumeContext().Publish(new + { + Id = context.Instance.FileId, + ParentId = context.Instance.ParentId, + BlobId = context.Instance.BlobId, + Bucket = context.Instance.Bucket, + CorrelationId = context.Instance.CorrelationId, + TimeStamp = DateTimeOffset.UtcNow + }); + }) + .Finalize() + ); + + SetCompletedWhenFinalized(); + } + + public Event ProcessFile { get; private set; } + Event ImageGenerated { get; set; } + Event ImageGenerationFailed { get; set; } + Event ImageAdded { get; set; } + Event MetadataExtracted { get; set; } + Event MetadataExtractionFailed { get; set; } + Event MetadataPersisted { get; set; } + Event FileProcessed { get; set; } + Event StatusChanged { get; set; } + Event NodeStatusPersisted { get; set; } + Event StatusPersisted { get; set; } + + State Processing { get; set; } + Event BeginProcessing { get; set; } + Event EndProcessing { get; set; } + State Processed { get; set; } + Event BeginProcessed { get; set; } + Event EndProcessed { get; set; } + + Event AllPersisted { get; set; } + Event NodeStatusPersistenceDone { get; set; } + Event StatusPersistenceDone { get; set; } + Event ImageGenerationFinished { get; set; } + Event MetadataExtractionFinished { get; set; } + } +} diff --git a/Nuget.config b/Nuget.config index bfc7af6..e99c073 100644 --- a/Nuget.config +++ b/Nuget.config @@ -4,7 +4,6 @@ - \ No newline at end of file diff --git a/OsdrService.sln b/OsdrService.sln index 30224bb..6e2e61b 100644 --- a/OsdrService.sln +++ b/OsdrService.sln @@ -47,9 +47,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sds.Osdr.Counters", "Sds.Os EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sds.Osdr.IntegrationTests", "Sds.Osdr.IntegrationTests\Sds.Osdr.IntegrationTests.csproj", "{A4B2F5F6-FCDE-4F9F-81C8-026531B87614}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sds.Osdr.WebApi.IntegrationTests", "Sds.Osdr.WebApi.IntegrationTests\Sds.Osdr.WebApi.IntegrationTests.csproj", "{A35262F5-BB2C-4EB6-9F65-612E2E052405}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Leanda.Microscopy", "Leanda.Microscopy\Leanda.Microscopy.csproj", "{3D8AEE6B-0B9B-4605-809F-8DFF462F2851}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sds.Osdr.EndToEndTests", "Sds.Osdr.EndToEndTests\Sds.Osdr.EndToEndTests.csproj", "{B5E3C323-D543-4ACB-B29D-5527C26C269D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sds.Osdr.WebApi.IntegrationTests", "Sds.Osdr.WebApi.IntegrationTests\Sds.Osdr.WebApi.IntegrationTests.csproj", "{B5C769A9-D973-461D-802A-D9B334474A69}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sds.Osdr.EndToEndTests", "Sds.Osdr.EndToEndTests\Sds.Osdr.EndToEndTests.csproj", "{FBB6AE94-F960-420C-A56D-507E6FF3C66B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Leanda.CategoryTree", "Leanda.Categories\Leanda.CategoryTree.csproj", "{3DDC1FE0-5DEC-4CCB-80A1-C7EC8C83305C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -139,14 +143,22 @@ Global {A4B2F5F6-FCDE-4F9F-81C8-026531B87614}.Debug|Any CPU.Build.0 = Debug|Any CPU {A4B2F5F6-FCDE-4F9F-81C8-026531B87614}.Release|Any CPU.ActiveCfg = Release|Any CPU {A4B2F5F6-FCDE-4F9F-81C8-026531B87614}.Release|Any CPU.Build.0 = Release|Any CPU - {A35262F5-BB2C-4EB6-9F65-612E2E052405}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A35262F5-BB2C-4EB6-9F65-612E2E052405}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A35262F5-BB2C-4EB6-9F65-612E2E052405}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A35262F5-BB2C-4EB6-9F65-612E2E052405}.Release|Any CPU.Build.0 = Release|Any CPU - {B5E3C323-D543-4ACB-B29D-5527C26C269D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B5E3C323-D543-4ACB-B29D-5527C26C269D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B5E3C323-D543-4ACB-B29D-5527C26C269D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B5E3C323-D543-4ACB-B29D-5527C26C269D}.Release|Any CPU.Build.0 = Release|Any CPU + {3D8AEE6B-0B9B-4605-809F-8DFF462F2851}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D8AEE6B-0B9B-4605-809F-8DFF462F2851}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D8AEE6B-0B9B-4605-809F-8DFF462F2851}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D8AEE6B-0B9B-4605-809F-8DFF462F2851}.Release|Any CPU.Build.0 = Release|Any CPU + {B5C769A9-D973-461D-802A-D9B334474A69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5C769A9-D973-461D-802A-D9B334474A69}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5C769A9-D973-461D-802A-D9B334474A69}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5C769A9-D973-461D-802A-D9B334474A69}.Release|Any CPU.Build.0 = Release|Any CPU + {FBB6AE94-F960-420C-A56D-507E6FF3C66B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBB6AE94-F960-420C-A56D-507E6FF3C66B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBB6AE94-F960-420C-A56D-507E6FF3C66B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBB6AE94-F960-420C-A56D-507E6FF3C66B}.Release|Any CPU.Build.0 = Release|Any CPU + {3DDC1FE0-5DEC-4CCB-80A1-C7EC8C83305C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DDC1FE0-5DEC-4CCB-80A1-C7EC8C83305C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DDC1FE0-5DEC-4CCB-80A1-C7EC8C83305C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DDC1FE0-5DEC-4CCB-80A1-C7EC8C83305C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -165,6 +177,8 @@ Global {4FCB7325-71AB-4DF8-9A74-FF80BC4237EE} = {A31B3DFD-691D-4C71-ACC5-E404C182697F} {CDB871C2-A0DA-4237-8C82-C5DDA928FF92} = {A31B3DFD-691D-4C71-ACC5-E404C182697F} {B16ED668-D36C-4528-AAE0-D6BA388C65E8} = {A31B3DFD-691D-4C71-ACC5-E404C182697F} + {3D8AEE6B-0B9B-4605-809F-8DFF462F2851} = {A31B3DFD-691D-4C71-ACC5-E404C182697F} + {3DDC1FE0-5DEC-4CCB-80A1-C7EC8C83305C} = {A31B3DFD-691D-4C71-ACC5-E404C182697F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A9717E96-83CC-4FA3-BFC4-B79F6D069442} diff --git a/README.md b/README.md new file mode 100644 index 0000000..de1dd16 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Leanda Core Services + +Leanda core services + +[![Build Status](https://travis-ci.org/ArqiSoft/leanda-core.svg?branch=master)](https://travis-ci.org/ArqiSoft/leanda-core) diff --git a/Sds.Osdr.Chemicals/Domain/Aggregates/Substance.cs b/Sds.Osdr.Chemicals/Domain/Aggregates/Substance.cs index 7f29bf1..330d17f 100644 --- a/Sds.Osdr.Chemicals/Domain/Aggregates/Substance.cs +++ b/Sds.Osdr.Chemicals/Domain/Aggregates/Substance.cs @@ -14,11 +14,6 @@ private void Apply(SubstanceCreated e) { } - //private void Apply(StandardizedBlobIdChanged e) - //{ - // StandardizedBlobId = e.BlobId; - //} - protected Substance() { } @@ -28,10 +23,5 @@ public Substance(Guid id, string bucket, Guid blobId, Guid userId, Guid fileId, { ApplyChange(new SubstanceCreated(Id, userId)); } - - //public void SetStandardizedBlobId(Guid userId, Guid blobId) - //{ - // ApplyChange(new StandardizedBlobIdChanged(Id, userId, blobId)); - //} } } diff --git a/Sds.Osdr.Counters/Sds.Osdr.Counters.csproj b/Sds.Osdr.Counters/Sds.Osdr.Counters.csproj index bde9888..663eb32 100644 --- a/Sds.Osdr.Counters/Sds.Osdr.Counters.csproj +++ b/Sds.Osdr.Counters/Sds.Osdr.Counters.csproj @@ -5,6 +5,24 @@ Debug;Release;Dev + + + + + + + + + + + + + + + + + + diff --git a/Sds.Osdr.Domain.BackEnd/Dockerfile b/Sds.Osdr.Domain.BackEnd/Dockerfile index ab364c0..c116b65 100644 --- a/Sds.Osdr.Domain.BackEnd/Dockerfile +++ b/Sds.Osdr.Domain.BackEnd/Dockerfile @@ -5,6 +5,8 @@ ARG RID=linux-x64 WORKDIR /build +COPY Leanda.Microscopy/Leanda.Microscopy.csproj Leanda.Microscopy/ +COPY Leanda.Categories/Leanda.CategoryTree.csproj Leanda.Categories/ COPY Sds.Osdr.Chemicals/Sds.Osdr.Chemicals.csproj Sds.Osdr.Chemicals/ COPY Sds.Osdr.Crystals/Sds.Osdr.Crystals.csproj Sds.Osdr.Crystals/ COPY Sds.Osdr.Domain/Sds.Osdr.Domain.csproj Sds.Osdr.Domain/ @@ -24,6 +26,8 @@ COPY Nuget.config . RUN dotnet restore --configfile Nuget.config Sds.Osdr.Domain.BackEnd/Sds.Osdr.Domain.BackEnd.csproj +COPY Leanda.Microscopy Leanda.Microscopy +COPY Leanda.Categories Leanda.Categories COPY Sds.Osdr.Chemicals Sds.Osdr.Chemicals COPY Sds.Osdr.Crystals Sds.Osdr.Crystals COPY Sds.Osdr.Domain Sds.Osdr.Domain @@ -45,7 +49,7 @@ RUN dotnet publish Sds.Osdr.Domain.BackEnd/Sds.Osdr.Domain.BackEnd.csproj -r $RI # Build runtime image FROM microsoft/dotnet:2.1-runtime-deps -LABEL maintainer="pshenichnov@gmail.com" +LABEL maintainer="info@arqisoft.com" WORKDIR /app diff --git a/Sds.Osdr.Domain.BackEnd/DomainBackEndService.cs b/Sds.Osdr.Domain.BackEnd/DomainBackEndService.cs index dfcdeb4..092a9c1 100644 --- a/Sds.Osdr.Domain.BackEnd/DomainBackEndService.cs +++ b/Sds.Osdr.Domain.BackEnd/DomainBackEndService.cs @@ -87,7 +87,9 @@ public void Start() Assembly.LoadFrom("Sds.Osdr.Office.dll"), Assembly.LoadFrom("Sds.Osdr.Tabular.dll"), Assembly.LoadFrom("Sds.Osdr.MachineLearning.dll"), - Assembly.LoadFrom("Sds.Osdr.WebPage.dll") + Assembly.LoadFrom("Sds.Osdr.WebPage.dll"), + Assembly.LoadFrom("Leanda.Microscopy.dll"), + Assembly.LoadFrom("Leanda.CategoryTree.dll"), }; Log.Information($"Registered modules:"); diff --git a/Sds.Osdr.Domain.BackEnd/Nuget.config b/Sds.Osdr.Domain.BackEnd/Nuget.config index bfc7af6..e99c073 100644 --- a/Sds.Osdr.Domain.BackEnd/Nuget.config +++ b/Sds.Osdr.Domain.BackEnd/Nuget.config @@ -4,7 +4,6 @@ - \ No newline at end of file diff --git a/Sds.Osdr.Domain.BackEnd/Sds.Osdr.Domain.BackEnd.csproj b/Sds.Osdr.Domain.BackEnd/Sds.Osdr.Domain.BackEnd.csproj index 6ff8d15..12977bc 100644 --- a/Sds.Osdr.Domain.BackEnd/Sds.Osdr.Domain.BackEnd.csproj +++ b/Sds.Osdr.Domain.BackEnd/Sds.Osdr.Domain.BackEnd.csproj @@ -20,13 +20,13 @@ - + - + - + @@ -40,6 +40,8 @@ + + diff --git a/Sds.Osdr.Domain.BackEnd/appsettings.json b/Sds.Osdr.Domain.BackEnd/appsettings.json index 70ba0e6..7fed849 100644 --- a/Sds.Osdr.Domain.BackEnd/appsettings.json +++ b/Sds.Osdr.Domain.BackEnd/appsettings.json @@ -1,14 +1,24 @@ { - "MongoDb": { - "ConnectionString": "%OSDR_MONGO_DB%", - "DatabaseName": "osdr_dev" - }, "Redis": { "ConnectionString": "%OSDR_REDIS%" }, "EventStore": { "ConnectionString": "%OSDR_EVENT_STORE%" }, + "Capabilities": [ + "Sds.Osdr.RecordsFile", + "Sds.Osdr.Chemicals", + "Sds.Osdr.Crystals", + "Sds.Osdr.Reactions", + "Sds.Osdr.Spectra", + "Sds.Osdr.Pdf", + "Sds.Osdr.Images", + "Sds.Osdr.Office", + "Sds.Osdr.Tabular", + "Sds.Osdr.MachineLearning", + "Sds.Osdr.WebPage", + "Leanda.Microscopy" + ], "MassTransit": { "ConnectionString": "%OSDR_RABBIT_MQ%", "PrefetchCount": 64, diff --git a/Sds.Osdr.Domain.FrontEnd/Dockerfile b/Sds.Osdr.Domain.FrontEnd/Dockerfile index 90fb8b9..4b1d5a1 100644 --- a/Sds.Osdr.Domain.FrontEnd/Dockerfile +++ b/Sds.Osdr.Domain.FrontEnd/Dockerfile @@ -5,6 +5,8 @@ ARG RID=linux-x64 WORKDIR /build +COPY Leanda.Microscopy/Leanda.Microscopy.csproj Leanda.Microscopy/ +COPY Leanda.Categories/Leanda.CategoryTree.csproj Leanda.Categories/ COPY Sds.Osdr.Chemicals/Sds.Osdr.Chemicals.csproj Sds.Osdr.Chemicals/ COPY Sds.Osdr.Crystals/Sds.Osdr.Crystals.csproj Sds.Osdr.Crystals/ COPY Sds.Osdr.Domain/Sds.Osdr.Domain.csproj Sds.Osdr.Domain/ @@ -24,6 +26,8 @@ COPY Nuget.config . RUN dotnet restore --configfile Nuget.config Sds.Osdr.Domain.FrontEnd/Sds.Osdr.Domain.FrontEnd.csproj +COPY Leanda.Microscopy Leanda.Microscopy +COPY Leanda.Categories Leanda.Categories COPY Sds.Osdr.Chemicals Sds.Osdr.Chemicals COPY Sds.Osdr.Crystals Sds.Osdr.Crystals COPY Sds.Osdr.Domain Sds.Osdr.Domain diff --git a/Sds.Osdr.Domain.FrontEnd/DomainFrontEndService.cs b/Sds.Osdr.Domain.FrontEnd/DomainFrontEndService.cs index cbcd5e8..fbde8b4 100644 --- a/Sds.Osdr.Domain.FrontEnd/DomainFrontEndService.cs +++ b/Sds.Osdr.Domain.FrontEnd/DomainFrontEndService.cs @@ -88,6 +88,7 @@ public void Start() Assembly.LoadFrom("Sds.Osdr.Tabular.dll"), Assembly.LoadFrom("Sds.Osdr.MachineLearning.dll"), Assembly.LoadFrom("Sds.Osdr.WebPage.dll"), + Assembly.LoadFrom("Leanda.Microscopy.dll"), }; Log.Information($"Registered modules:"); diff --git a/Sds.Osdr.Domain.FrontEnd/Nuget.config b/Sds.Osdr.Domain.FrontEnd/Nuget.config index bfc7af6..e99c073 100644 --- a/Sds.Osdr.Domain.FrontEnd/Nuget.config +++ b/Sds.Osdr.Domain.FrontEnd/Nuget.config @@ -4,7 +4,6 @@ - \ No newline at end of file diff --git a/Sds.Osdr.Domain.FrontEnd/Sds.Osdr.Domain.FrontEnd.csproj b/Sds.Osdr.Domain.FrontEnd/Sds.Osdr.Domain.FrontEnd.csproj index 65f3602..e569584 100644 --- a/Sds.Osdr.Domain.FrontEnd/Sds.Osdr.Domain.FrontEnd.csproj +++ b/Sds.Osdr.Domain.FrontEnd/Sds.Osdr.Domain.FrontEnd.csproj @@ -18,12 +18,12 @@ - + - + - + @@ -35,6 +35,7 @@ + diff --git a/Sds.Osdr.Domain.FrontEnd/appsettings.json b/Sds.Osdr.Domain.FrontEnd/appsettings.json index 9725240..5f94e2b 100644 --- a/Sds.Osdr.Domain.FrontEnd/appsettings.json +++ b/Sds.Osdr.Domain.FrontEnd/appsettings.json @@ -2,10 +2,6 @@ "Redis": { "ConnectionString": "%OSDR_REDIS%" }, - "ConnectionSettings": { - "ConnectionString": "%OSDR_MONGO_DB%", - "DatabaseName": "osdr_dev" - }, "EventStore": { "ConnectionString": "%OSDR_EVENT_STORE%" }, diff --git a/Sds.Osdr.Domain.SagaHost/Dockerfile b/Sds.Osdr.Domain.SagaHost/Dockerfile index 5fd6d1f..ccbe689 100644 --- a/Sds.Osdr.Domain.SagaHost/Dockerfile +++ b/Sds.Osdr.Domain.SagaHost/Dockerfile @@ -5,6 +5,8 @@ ARG RID=linux-x64 WORKDIR /build +COPY Leanda.Microscopy/Leanda.Microscopy.csproj Leanda.Microscopy/ +COPY Leanda.Categories/Leanda.CategoryTree.csproj Leanda.Categories/ COPY Sds.Osdr.Chemicals/Sds.Osdr.Chemicals.csproj Sds.Osdr.Chemicals/ COPY Sds.Osdr.Crystals/Sds.Osdr.Crystals.csproj Sds.Osdr.Crystals/ COPY Sds.Osdr.Domain/Sds.Osdr.Domain.csproj Sds.Osdr.Domain/ @@ -24,6 +26,8 @@ COPY Nuget.config . RUN dotnet restore --configfile Nuget.config Sds.Osdr.Domain.SagaHost/Sds.Osdr.Domain.SagaHost.csproj +COPY Leanda.Microscopy Leanda.Microscopy +COPY Leanda.Categories Leanda.Categories COPY Sds.Osdr.Chemicals Sds.Osdr.Chemicals COPY Sds.Osdr.Crystals Sds.Osdr.Crystals COPY Sds.Osdr.Domain Sds.Osdr.Domain diff --git a/Sds.Osdr.Domain.SagaHost/DomainSagaHostService.cs b/Sds.Osdr.Domain.SagaHost/DomainSagaHostService.cs index 8d5fa5f..7089687 100644 --- a/Sds.Osdr.Domain.SagaHost/DomainSagaHostService.cs +++ b/Sds.Osdr.Domain.SagaHost/DomainSagaHostService.cs @@ -84,6 +84,7 @@ public void Start() Assembly.LoadFrom("Sds.Osdr.Tabular.dll"), Assembly.LoadFrom("Sds.Osdr.MachineLearning.dll"), Assembly.LoadFrom("Sds.Osdr.WebPage.dll"), + Assembly.LoadFrom("Leanda.Microscopy.dll"), }; Log.Information($"Registered modules:"); diff --git a/Sds.Osdr.Domain.SagaHost/Sds.Osdr.Domain.SagaHost.csproj b/Sds.Osdr.Domain.SagaHost/Sds.Osdr.Domain.SagaHost.csproj index f13d2fc..9abb47b 100644 --- a/Sds.Osdr.Domain.SagaHost/Sds.Osdr.Domain.SagaHost.csproj +++ b/Sds.Osdr.Domain.SagaHost/Sds.Osdr.Domain.SagaHost.csproj @@ -11,8 +11,8 @@ - - + + @@ -25,6 +25,7 @@ + diff --git a/Sds.Osdr.Domain.SagaHost/appsettings.json b/Sds.Osdr.Domain.SagaHost/appsettings.json index 07b93fe..6f72435 100644 --- a/Sds.Osdr.Domain.SagaHost/appsettings.json +++ b/Sds.Osdr.Domain.SagaHost/appsettings.json @@ -1,7 +1,6 @@ { "MongoDb": { - "ConnectionString": "%OSDR_MONGO_DB%", - "DatabaseName": "osdr_dev" + "ConnectionString": "%OSDR_MONGO_DB%" }, "MassTransit": { "ConnectionString": "%OSDR_RABBIT_MQ%", diff --git a/Sds.Osdr.Domain/KeyValue.cs b/Sds.Osdr.Domain/KeyValue.cs new file mode 100644 index 0000000..1a940ed --- /dev/null +++ b/Sds.Osdr.Domain/KeyValue.cs @@ -0,0 +1,8 @@ +namespace Sds.Osdr.Domain +{ + public class KeyValue + { + public string Name { get; set; } + public T Value { get; set; } + } +} diff --git a/Sds.Osdr.Domain/Sds.Osdr.Domain.csproj b/Sds.Osdr.Domain/Sds.Osdr.Domain.csproj index 6bdc398..e475e50 100644 --- a/Sds.Osdr.Domain/Sds.Osdr.Domain.csproj +++ b/Sds.Osdr.Domain/Sds.Osdr.Domain.csproj @@ -10,13 +10,16 @@ SDS OSDR domain models false First release - Copyright 2017 (c) Science Data Software. All rights reserved. + domain OSDR - Debug;Release;Dev + Debug;Release;Dev + true + 0.12.1 + https://opensource.org/licenses/MIT - + diff --git a/Sds.Osdr.EndToEndTests/.env b/Sds.Osdr.EndToEndTests/.env index 6004b01..c4f0cb3 100644 --- a/Sds.Osdr.EndToEndTests/.env +++ b/Sds.Osdr.EndToEndTests/.env @@ -1,2 +1,2 @@ OSDR_LOG_FOLDER=D:\Projects\SDS\_Logs -TAG_VERSION=stable \ No newline at end of file +TAG_VERSION=latest \ No newline at end of file diff --git a/Sds.Osdr.EndToEndTests/Dockerfile b/Sds.Osdr.EndToEndTests/Dockerfile index 1cfdda0..42f7dff 100644 --- a/Sds.Osdr.EndToEndTests/Dockerfile +++ b/Sds.Osdr.EndToEndTests/Dockerfile @@ -4,6 +4,8 @@ ARG RID=linux-x64 WORKDIR /build +COPY Leanda.Microscopy/Leanda.Microscopy.csproj Leanda.Microscopy/ +COPY Leanda.Categories/Leanda.CategoryTree.csproj Leanda.Categories/ COPY Sds.Osdr.Chemicals/Sds.Osdr.Chemicals.csproj Sds.Osdr.Chemicals/ COPY Sds.Osdr.Crystals/Sds.Osdr.Crystals.csproj Sds.Osdr.Crystals/ COPY Sds.Osdr.Domain/Sds.Osdr.Domain.csproj Sds.Osdr.Domain/ @@ -20,10 +22,13 @@ COPY Sds.Osdr.Tabular/Sds.Osdr.Tabular.csproj Sds.Osdr.Tabular/ COPY Sds.Osdr.WebPage/Sds.Osdr.WebPage.csproj Sds.Osdr.WebPage/ COPY Sds.Osdr.IntegrationTests/Sds.Osdr.IntegrationTests.csproj Sds.Osdr.IntegrationTests/ COPY Sds.Osdr.WebApi.IntegrationTests/Sds.Osdr.WebApi.IntegrationTests.csproj Sds.Osdr.WebApi.IntegrationTests/ +COPY Sds.Osdr.EndToEndTests/Sds.Osdr.EndToEndTests.csproj Sds.Osdr.EndToEndTests/ COPY Nuget.config . -RUN dotnet restore --configfile Nuget.config Sds.Osdr.WebApi.IntegrationTests/Sds.Osdr.WebApi.IntegrationTests.csproj +RUN dotnet restore --configfile Nuget.config Sds.Osdr.EndToEndTests/Sds.Osdr.EndToEndTests.csproj +COPY Leanda.Microscopy Leanda.Microscopy +COPY Leanda.Categories Leanda.Categories COPY Sds.Osdr.Chemicals Sds.Osdr.Chemicals COPY Sds.Osdr.Crystals Sds.Osdr.Crystals COPY Sds.Osdr.Domain Sds.Osdr.Domain @@ -40,8 +45,9 @@ COPY Sds.Osdr.Tabular Sds.Osdr.Tabular COPY Sds.Osdr.WebPage Sds.Osdr.WebPage COPY Sds.Osdr.IntegrationTests Sds.Osdr.IntegrationTests COPY Sds.Osdr.WebApi.IntegrationTests Sds.Osdr.WebApi.IntegrationTests +COPY Sds.Osdr.EndToEndTests Sds.Osdr.EndToEndTests -RUN dotnet publish Sds.Osdr.WebApi.IntegrationTests/Sds.Osdr.WebApi.IntegrationTests.csproj -r $RID -c Release -o /dist +RUN dotnet publish Sds.Osdr.EndToEndTests/Sds.Osdr.EndToEndTests.csproj -r $RID -c Release -o /dist # Build runtime image FROM microsoft/dotnet:2.1-sdk @@ -55,4 +61,4 @@ RUN curl https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for COPY --from=build-env /dist ./ -ENTRYPOINT ["dotnet", "vstest", "./Sds.Osdr.WebApi.IntegrationTests.dll"] +ENTRYPOINT ["dotnet", "vstest", "./Sds.Osdr.EndToEndTests.dll"] diff --git a/Sds.Osdr.EndToEndTests/KeyCloak/keycloak-settings/osdr-realm.json b/Sds.Osdr.EndToEndTests/KeyCloak/keycloak-settings/osdr-realm.json index bef06b6..9673e00 100644 --- a/Sds.Osdr.EndToEndTests/KeyCloak/keycloak-settings/osdr-realm.json +++ b/Sds.Osdr.EndToEndTests/KeyCloak/keycloak-settings/osdr-realm.json @@ -18,7 +18,7 @@ { "type" : "password", "value" : "qqq123" } ], - "realmRoles": [ "user" ], + "realmRoles": [ "user", "leanda-admin" ], "clientRoles": { "account": ["view-profile", "manage-account"] } @@ -41,9 +41,22 @@ ], "roles" : { "realm" : [ + { + "id": "a52b7f6f-ccf4-4f48-ac55-38fe9d5509ec", + "name": "leanda-admin", + "description": "Administrator privileges", + "scopeParamRequired": true, + "composite": false, + "clientRole": false, + "containerId": "4ee57060-1936-4b39-b935-ec970d21d920" + } ] }, "scopeMappings": [ + { + "client": "osdr_webapi", + "roles": ["user", "leanda-admin"] + } ], "clients": [ { @@ -138,7 +151,8 @@ "consentText": "${fullName}", "config": { "id.token.claim": "true", - "access.token.claim": "true" + "access.token.claim": "true", + "userinfo.token.claim": "true" } }, { @@ -150,6 +164,7 @@ "consentText": "", "config": { "user.session.note": "clientHost", + "userinfo.token.claim": "true", "id.token.claim": "true", "access.token.claim": "true", "claim.name": "clientHost", @@ -165,6 +180,7 @@ "consentText": "", "config": { "user.session.note": "clientAddress", + "userinfo.token.claim": "true", "id.token.claim": "true", "access.token.claim": "true", "claim.name": "clientAddress", @@ -187,6 +203,21 @@ "jsonType.label": "String" } }, + { + "id": "0db369f9-2d25-4b73-86d8-bae9aa9e6ac4", + "name": "User realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "multivalued": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "user_role", + "jsonType.label": "String" + } + }, { "id": "ca1af84f-2f31-41d6-9b92-c2193882c870", "name": "role list", @@ -208,6 +239,7 @@ "consentText": "", "config": { "user.session.note": "clientId", + "userinfo.token.claim": "true", "id.token.claim": "true", "access.token.claim": "true", "claim.name": "clientId", @@ -259,8 +291,8 @@ "clientId": "osdr_ml_modeler", "surrogateAuthRequired": false, "enabled": true, - "clientAuthenticatorType": "client-secret", "secret": "osdr_ml_modeler_secret", + "clientAuthenticatorType": "client-secret", "redirectUris": [ "*" ], diff --git a/Sds.Osdr.EndToEndTests/OsdrTest.cs b/Sds.Osdr.EndToEndTests/OsdrTest.cs index 9025ab7..dfa889b 100644 --- a/Sds.Osdr.EndToEndTests/OsdrTest.cs +++ b/Sds.Osdr.EndToEndTests/OsdrTest.cs @@ -1,4 +1,5 @@ -using Sds.Osdr.IntegrationTests; +using Nest; +using Sds.Osdr.IntegrationTests; using Sds.Osdr.WebApi.IntegrationTests.EndPoints; using Serilog; using Serilog.Events; @@ -30,7 +31,7 @@ public OsdrWebTest(OsdrTestHarness fixture, ITestOutputHelper output = null) : b public OsdrWebClient JohnApi => WebFixture.JohnApi; public OsdrWebClient JaneApi => WebFixture.JaneApi; public OsdrWebClient UnauthorizedApi => WebFixture.UnauthorizedApi; - + public IElasticClient ElasticClient => WebFixture.ElasticClient; protected OsdrTestHarness WebFixture => Harness as OsdrTestHarness; } } \ No newline at end of file diff --git a/Sds.Osdr.EndToEndTests/OsdrTestHarness.cs b/Sds.Osdr.EndToEndTests/OsdrTestHarness.cs index 7343747..8040ed7 100644 --- a/Sds.Osdr.EndToEndTests/OsdrTestHarness.cs +++ b/Sds.Osdr.EndToEndTests/OsdrTestHarness.cs @@ -1,4 +1,7 @@ -using Sds.Osdr.WebApi.IntegrationTests.EndPoints; +using MassTransit.RabbitMqTransport; +using Microsoft.Extensions.DependencyInjection; +using Sds.Osdr.WebApi.IntegrationTests.EndPoints; +using System; using System.Net.Http; using System.Net.Http.Headers; @@ -33,6 +36,14 @@ public OsdrTestHarness() : base() UnauthorizedApi = new OsdrWebClient(UnauthorizedClient); } + protected override void OnInit(IServiceCollection services) + { + } + + protected override void OnBusCreation(IRabbitMqBusFactoryConfigurator config, IRabbitMqHost host, IServiceProvider container) + { + } + public override void Dispose() { JohnClient.Dispose(); diff --git a/Sds.Osdr.EndToEndTests/Tests/Categories/AddEntityCategories.cs b/Sds.Osdr.EndToEndTests/Tests/Categories/AddEntityCategories.cs new file mode 100644 index 0000000..e7190c8 --- /dev/null +++ b/Sds.Osdr.EndToEndTests/Tests/Categories/AddEntityCategories.cs @@ -0,0 +1,79 @@ +using FluentAssertions; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.Traits; +using Sds.Osdr.WebApi.IntegrationTests; +using Sds.Osdr.WebApi.IntegrationTests.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.EndToEndTests.Tests.Categories +{ + public class AddEntityCategoriesFixture + { + public Guid TreeId { get; set; } + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + + public AddEntityCategoriesFixture(OsdrTestHarness harness) + { + var categories = new List() + { + new TreeNode("Category Root", new List() + { + new TreeNode("My Test Category"), + new TreeNode("Projects Two") + }) + }; + + var response = harness.JohnApi.PostData("api/categorytrees/tree", categories).Result; + + var content = response.Content.ReadAsStringAsync().Result; + + TreeId = Guid.Parse(content); + + harness.WaitWhileCategoryTreePersisted(TreeId); + + BlobId = harness.JohnBlobStorageClient.AddResource(harness.JohnId.ToString(), "Chemical-diagram.png", new Dictionary() { { "parentId", harness.JohnId } }).Result; + + FileId = harness.WaitWhileFileProcessed(BlobId); + + // add category to entity + response = harness.JohnApi.PostData($"/api/categoryentities/entities/{FileId}/categories", new List { TreeId }).Result; + response.EnsureSuccessStatusCode(); + harness.WaitWhileCategoryIndexed(FileId.ToString()); + } + } + + [Collection("OSDR Test Harness")] + public class AddEntityCategories : OsdrWebTest, IClassFixture + { + private Guid TreeId; + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + + public AddEntityCategories(OsdrTestHarness harness, ITestOutputHelper output, AddEntityCategoriesFixture fixture) : base(harness, output) + { + TreeId = fixture.TreeId; + BlobId = fixture.BlobId; + FileId = fixture.FileId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task AddCategory_AddingCategoryToEntity_CategoryIdShouldAppearInCategoriesListForEntity() + { + var elasticSearchNodesRequest = await JohnApi.GetData($"/api/categoryentities/categories/{TreeId}"); + var elasticSearchNodes = await elasticSearchNodesRequest.Content.ReadAsJArrayAsync(); + elasticSearchNodes.Count.Should().Be(1); + elasticSearchNodes.Single().Value("id").Should().Be(FileId.ToString()); + } + } +} diff --git a/Sds.Osdr.EndToEndTests/Tests/Categories/CreateCategory.cs b/Sds.Osdr.EndToEndTests/Tests/Categories/CreateCategory.cs new file mode 100644 index 0000000..fc64ceb --- /dev/null +++ b/Sds.Osdr.EndToEndTests/Tests/Categories/CreateCategory.cs @@ -0,0 +1,89 @@ +using FluentAssertions; +using FluentAssertions.Json; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using Sds.Osdr.WebApi.IntegrationTests; +using Sds.Osdr.WebApi.IntegrationTests.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.EndToEndTests.Tests.Categories +{ + public class CreateCategoryTreeFixture + { + public Guid CategoryId; + + public CreateCategoryTreeFixture(OsdrTestHarness harness) + { + var categories = new List() + { + new TreeNode("Projects", new List() + { + new TreeNode("Projects One"), + new TreeNode("Projects Two") + }) + }; + + var response = harness.JohnApi.PostData("/api/categorytrees/tree", categories).Result; + + var content = response.Content.ReadAsStringAsync().Result; + + CategoryId = Guid.Parse(content); + + harness.WaitWhileCategoryTreePersisted(CategoryId); + } + } + + [Collection("OSDR Test Harness")] + public class CreateCategoryTree : OsdrWebTest, IClassFixture + { + private Guid CategoryId; + + public CreateCategoryTree(OsdrTestHarness harness, ITestOutputHelper output, CreateCategoryTreeFixture fixture) : base(harness, output) + { + CategoryId = fixture.CategoryId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTree_CreateNewCategoryTree_BuiltExpectedDocument() + { + var contentRequest = await JohnApi.GetData($"/api/categorytrees/tree/{CategoryId}"); + + var jsonCategory = await contentRequest.Content.ReadAsJObjectAsync(); + + jsonCategory.Should().HaveElement("id"); + jsonCategory["id"].Value().Should().Be(CategoryId.ToString()); + + jsonCategory.Should().HaveElement("createdBy"); + jsonCategory["createdBy"].Value().Should().Be(JohnId.ToString()); + + jsonCategory.Should().HaveElement("createdDateTime") + .And.HaveElement("createdDateTime") + .And.HaveElement("updatedDateTime"); + + jsonCategory.Should().HaveElement("version"); + jsonCategory["version"].Value().Should().Be(1); + + jsonCategory.Should().HaveElement("nodes"); + var treeNodes = jsonCategory["nodes"].Value(); + treeNodes.Should().HaveCount(1); + var mainNode = treeNodes.Single(); + mainNode.Should().HaveElement("title"); + mainNode["title"].Value().Should().Be("Projects"); + var insideNodes = mainNode["children"].Value(); + insideNodes.Should().HaveCount(2); + var titles = insideNodes.Select(i => i["title"].Value()); + titles.Should().Contain(new List { "Projects One", "Projects Two" }); + insideNodes[0].Should().HaveElement("id"); + insideNodes[1].Should().HaveElement("id"); + } + } +} diff --git a/Sds.Osdr.EndToEndTests/Tests/Categories/DeleteEntityCategory.cs b/Sds.Osdr.EndToEndTests/Tests/Categories/DeleteEntityCategory.cs new file mode 100644 index 0000000..9990d0b --- /dev/null +++ b/Sds.Osdr.EndToEndTests/Tests/Categories/DeleteEntityCategory.cs @@ -0,0 +1,107 @@ +using FluentAssertions; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.Traits; +using Sds.Osdr.WebApi.IntegrationTests; +using Sds.Osdr.WebApi.IntegrationTests.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.EndToEndTests.Tests.Categories +{ + public class DeleteEntityCategoryFixture + { + public Guid RootCategoryId; + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + + public DeleteEntityCategoryFixture(OsdrTestHarness harness) + { + var categories = new List() + { + new TreeNode("Category Root", new List() + { + new TreeNode("My Test Category"), + new TreeNode("Projects Two") + }) + }; + + var response = harness.JohnApi.PostData("api/categorytrees/tree", categories).Result; + + var content = response.Content.ReadAsStringAsync().Result; + + RootCategoryId = Guid.Parse(content); + + harness.WaitWhileCategoryTreePersisted(RootCategoryId); + + BlobId = harness.JohnBlobStorageClient.AddResource(harness.JohnId.ToString(), "Chemical-diagram.png", new Dictionary() { { "parentId", harness.JohnId } }).Result; + + FileId = harness.WaitWhileFileProcessed(BlobId); + } + } + + [Collection("OSDR Test Harness")] + public class DeleteEntityCategoryTest : OsdrWebTest, IClassFixture + { + private Guid RootCategoryId; + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + + public DeleteEntityCategoryTest(OsdrTestHarness harness, ITestOutputHelper output, DeleteEntityCategoryFixture fixture) : base(harness, output) + { + RootCategoryId = fixture.RootCategoryId; + BlobId = fixture.BlobId; + FileId = fixture.FileId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task DeleteCategory_DeleteOneCategoryFromEntity_CategoryIdShouldBeRemovedFromEntity() + { + var fileNodeResponse = await JohnApi.GetNodeById(FileId); + var fileNode = await fileNodeResponse.Content.ReadAsJObjectAsync(); + var fileNodeId = Guid.Parse(fileNode.Value("id")); + + var treeRequest = await JohnApi.GetData($"api/categorytrees/tree/{RootCategoryId}"); + var treeContent = await treeRequest.Content.ReadAsJObjectAsync(); + var categoryId1 = treeContent["nodes"][0]["children"][0]["id"].ToString(); + var categoryId2 = treeContent["nodes"][0]["children"][1]["id"].ToString(); + + // add categories to entity + await JohnApi.PostData($"/api/categoryentities/entities/{fileNodeId}/categories", new List { categoryId1, categoryId2 }); + WebFixture.WaitWhileCategoryIndexed(fileNodeId.ToString()); + + var firstCategoryAddedNode = await GetNodeByCategoryId(categoryId1); + firstCategoryAddedNode.Value("id").Should().Be(fileNodeId.ToString()); + + // delete first category from node + await JohnApi.DeleteData($"/api/categoryentities/entities/{fileNodeId}/categories/{categoryId1}"); + // check if node contains categoryId + WebFixture.WaitWhileCategoryDeleted(categoryId1); + var firstCategoryDeletedNode = await GetNodeByCategoryId(categoryId1); + firstCategoryDeletedNode.Should().BeNull(); + + var entityCategoryIdsRequest = await JohnApi.GetData($"/api/categoryentities/entities/{fileNodeId}/categories"); + var entityCategoryIds = await entityCategoryIdsRequest.Content.ReadAsJArrayAsync(); + entityCategoryIds.Should().HaveCount(1); + entityCategoryIds.Single().Value("id").Should().Be(categoryId2); + } + + private async Task GetNodeByCategoryId(string categoryId) + { + var nodesResponseContent = await JohnApi.GetData($"/api/categoryentities/categories/{categoryId}"); + var elasticSearchNodes = await nodesResponseContent.Content.ReadAsJArrayAsync(); + if (elasticSearchNodes.Count() != 0) + return elasticSearchNodes.First(); + + return null; + } + } +} diff --git a/Sds.Osdr.EndToEndTests/Tests/Categories/GetCategoriesIdsByEntityId.cs b/Sds.Osdr.EndToEndTests/Tests/Categories/GetCategoriesIdsByEntityId.cs new file mode 100644 index 0000000..c046f23 --- /dev/null +++ b/Sds.Osdr.EndToEndTests/Tests/Categories/GetCategoriesIdsByEntityId.cs @@ -0,0 +1,79 @@ +using FluentAssertions; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.Traits; +using Sds.Osdr.WebApi.IntegrationTests; +using Sds.Osdr.WebApi.IntegrationTests.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.EndToEndTests.Tests.Categories +{ + public class GetCategoriesIdsByEntityIdFixture + { + public Guid CategoryId; + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + + + public GetCategoriesIdsByEntityIdFixture(OsdrTestHarness harness) + { + var categories = new List() + { + new TreeNode("Category Root", new List() + { + new TreeNode("My Test Category"), + new TreeNode("Projects Two") + }) + }; + + var response = harness.JohnApi.PostData("api/categorytrees/tree", categories).Result; + + var content = response.Content.ReadAsStringAsync().Result; + + CategoryId = Guid.Parse(content); + + harness.WaitWhileCategoryTreePersisted(CategoryId); + + BlobId = harness.JohnBlobStorageClient.AddResource(harness.JohnId.ToString(), "Chemical-diagram.png", new Dictionary() { { "parentId", harness.JohnId } }).Result; + + FileId = harness.WaitWhileFileProcessed(BlobId); + + harness.JohnApi.PostData($"/api/categoryentities/entities/{FileId}/categories", new List { CategoryId }).Wait(); + + harness.WaitWhileCategoryIndexed(CategoryId.ToString()); + } + } + + [Collection("OSDR Test Harness")] + public class GetCategoriesIdsByEntityId : OsdrWebTest, IClassFixture + { + private Guid CategoryId; + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + + + public GetCategoriesIdsByEntityId(OsdrTestHarness harness, ITestOutputHelper output, GetCategoriesIdsByEntityIdFixture fixture) : base(harness, output) + { + CategoryId = fixture.CategoryId; + BlobId = fixture.BlobId; + FileId = fixture.FileId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryEntities_GetCategoriesIdsByEntityId_ShouldReturnExpectedCategoryIds() + { + var content = await JohnApi.GetData($"/api/categoryentities/entities/{FileId}/categories"); + var categoriesIds = await content.Content.ReadAsJArrayAsync(); + categoriesIds.Any(x => x.Value() == CategoryId.ToString()).Should().BeTrue(); + } + } +} diff --git a/Sds.Osdr.EndToEndTests/Tests/Categories/GetEntitiesByCategoryId.cs b/Sds.Osdr.EndToEndTests/Tests/Categories/GetEntitiesByCategoryId.cs new file mode 100644 index 0000000..3c04222 --- /dev/null +++ b/Sds.Osdr.EndToEndTests/Tests/Categories/GetEntitiesByCategoryId.cs @@ -0,0 +1,43 @@ +using FluentAssertions; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.Traits; +using Sds.Osdr.WebApi.IntegrationTests; +using Sds.Osdr.WebApi.IntegrationTests.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.EndToEndTests.Tests.Categories +{ + [Collection("OSDR Test Harness")] + public class GetEntitiesByCategoryId : OsdrWebTest, IClassFixture + { + private Guid CategoryId; + public Guid FileId { get; set; } + + + public GetEntitiesByCategoryId(OsdrTestHarness harness, ITestOutputHelper output, GetCategoriesIdsByEntityIdFixture fixture) : base(harness, output) + { + CategoryId = fixture.CategoryId; + FileId = fixture.FileId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task EntityCategories_GetCategoriesIdsByEntityId_ShouldReturnEntityWithExpectedId() + { + var entitiesRequest = await JohnApi.GetData($"/api/categoryentities/categories/{CategoryId}"); + var entities = await entitiesRequest.Content.ReadAsJArrayAsync(); + entities.Should().HaveCount(1); + var entity = entities.Single(); + entity["id"].Value().Should().Be(FileId.ToString()); + } + } +} diff --git a/Sds.Osdr.EndToEndTests/Tests/Substances/ValidCdxProcessing.cs b/Sds.Osdr.EndToEndTests/Tests/Substances/ValidCdxProcessing.cs index 857a15d..8ac8d82 100644 --- a/Sds.Osdr.EndToEndTests/Tests/Substances/ValidCdxProcessing.cs +++ b/Sds.Osdr.EndToEndTests/Tests/Substances/ValidCdxProcessing.cs @@ -107,7 +107,7 @@ public async Task ChemicalProcessing_ValidCdx_GenerateExpectedFileNode() fileNode["images"].Should().NotBeNull(); fileNode["images"].Should().HaveCount(1); } - [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Chemical)] + [Fact(Skip ="Unstable"), WebApiTrait(TraitGroup.All, TraitGroup.Chemical)] public async Task ChemicalProcessing_ValidCdx_GenerateExpectedRecordNode() { var recordResponse = await JohnApi.GetNodesById(FileId); diff --git a/Sds.Osdr.EndToEndTests/Tests/Substances/ValidMolProcessing.cs b/Sds.Osdr.EndToEndTests/Tests/Substances/ValidMolProcessing.cs index 1e8421e..9ea2813 100644 --- a/Sds.Osdr.EndToEndTests/Tests/Substances/ValidMolProcessing.cs +++ b/Sds.Osdr.EndToEndTests/Tests/Substances/ValidMolProcessing.cs @@ -194,7 +194,7 @@ public async Task ChemicalProcessing_ValidMol_GenerateExpectedRecordEntity() recordEntity["images"].Should().NotBeNull(); recordEntity["images"].Should().HaveCount(1); } - [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Chemical)] + [Fact(Skip ="Ustable"), WebApiTrait(TraitGroup.All, TraitGroup.Chemical)] public async Task ChemicalProcessing_ValidMol_GenerateExpectedRecordNode() { var recordResponse = await JohnApi.GetNodesById(FileId); diff --git a/Sds.Osdr.EndToEndTests/appsettings.json b/Sds.Osdr.EndToEndTests/appsettings.json index e2d542e..b8e2070 100644 --- a/Sds.Osdr.EndToEndTests/appsettings.json +++ b/Sds.Osdr.EndToEndTests/appsettings.json @@ -1,4 +1,7 @@ { + "KeyCloak": { + "Authority": "%IDENTITY_SERVER_URL%" + }, "OsdrWebApi": { "Base": "http://localhost:28611" }, @@ -14,6 +17,9 @@ "EventStore": { "ConnectionString": "%OSDR_EVENT_STORE%" }, + "ElasticSearch": { + "ConnectionString": "%OSDR_ES%" + }, "MassTransit": { "ConnectionString": "%OSDR_RABBIT_MQ%", "PrefetchCount": 64, diff --git a/Sds.Osdr.EndToEndTests/docker-compose.yml b/Sds.Osdr.EndToEndTests/docker-compose.yml index ef0c5d2..22b6fb2 100644 --- a/Sds.Osdr.EndToEndTests/docker-compose.yml +++ b/Sds.Osdr.EndToEndTests/docker-compose.yml @@ -7,9 +7,9 @@ services: - "2113:2113" - "1113:1113" environment: - - RUN_PROJECTIONS = All + - RUN_PROJECTIONS=All networks: - - osdr-test + - leanda-net redis: image: redis:4-alpine @@ -17,25 +17,25 @@ services: # ports: # - "6379:6379" networks: - - osdr-test + - leanda-net rabbitmq: - image: docker.your-company.com/osdr-rabbitmq:3.6 - hostname: "rabbitmq-test" + image: leanda/rabbitmq + hostname: "rabbitmq-leanda" environment: - - RABBITMQ_DEFAULT_VHOST=osdr_test + - RABBITMQ_DEFAULT_VHOST=leanda ports: - "8282:15672" - "5672:5672" networks: - - osdr-test + - leanda-net mongo: image: mongo:3.6 ports: - "27017:27017" networks: - - osdr-test + - leanda-net postgres: image: postgres @@ -46,7 +46,7 @@ services: POSTGRES_ROOT_PASSWORD: keycloak pgdata: data-pstgresql networks: - - osdr-test + - leanda-net keycloak: build: KeyCloak @@ -62,68 +62,67 @@ services: ports: - '8080:8080' networks: - - osdr-test + - leanda-net depends_on: - postgres + + elasticsearch: + image: leanda/elasticsearch + container_name: elasticsearch + environment: + - discovery.type=single-node + ports: + - "9201:9201" + - "9200:9200" + - "9301:9300" + networks: + - leanda-net - # imaging-persistence: - # container_name: imaging-persistence - # image: docker.your-company.com/imaging-persistence:${TAG_VERSION-latest} - # environment: - # - OSDR_LOG_FOLDER=/logs - # - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev - # - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test - # volumes: - # - ${OSDR_LOG_FOLDER}:/logs - # networks: - # - osdr-test - # depends_on: - # - rabbitmq - # - mongo - - metadata-storage-processing: - container_name: metadata-storage-processing - image: docker.your-company.com/metadata-storage-processing:${TAG_VERSION-latest} + metadata-processing: + container_name: metadata-processing + image: leanda/metadata-processing:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./Sds.MetadataStorage.Processing volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test + - leanda-net depends_on: - rabbitmq - mongo - osdr-indexing: - container_name: osdr-indexing - image: docker.your-company.com/indexing:${TAG_VERSION-latest} + indexing: + container_name: indexing + image: leanda/indexing:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - OSDR_ES=http://elasticsearch:9200 - command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./Sds.Indexing + command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./wait-for-it.sh elasticsearch:9200 -t 60 -- ./Sds.Indexing volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test + - leanda-net depends_on: + - elasticsearch + - mongo - rabbitmq - imaging-service: - container_name: osdr-imaging-service - image: docker.your-company.com/sds/osdr-imaging-service:${TAG_VERSION-latest} + imaging: + container_name: imaging + image: leanda/imaging:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - OSDR_LOG_FOLDER=/logs - OSDR_TEMP_FILES_FOLDER=/temp - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda - QUEUE_PREFETCH_SIZE=9 - EXECUTOR_THREAD_COUNT=3 command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./wait-for-it.sh mongo:27017 -t 30 -- java -Djava.awt.headless=true -Xmx256m -XX:NativeMemoryTracking=summary -jar sds-imaging-service.jar @@ -131,87 +130,87 @@ services: - ${OSDR_LOG_FOLDER}:/logs - ${OSDR_TEMP_FILES_FOLDER}:/temp networks: - - osdr-test + - leanda-net depends_on: - rabbitmq - mongo - # reaction-parser-service: - # container_name: reaction-file-parser - # image: docker.your-company.com/sds/reaction-file-parser:${TAG_VERSION-latest} - # entrypoint: /bin/bash - # environment: - # - TZ=EST - # - OSDR_LOG_FOLDER=/logs - # - OSDR_TEMP_FILES_FOLDER=/temp - # - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test - # - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev - # - QUEUE_PREFETCH_SIZE=9 - # - EXECUTOR_THREAD_COUNT=3 - # command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./wait-for-it.sh mongo:27017 -t 30 -- java -XX:NativeMemoryTracking=summary -jar reaction-parser.jar - # volumes: - # - ${OSDR_LOG_FOLDER}:/logs - # - ${OSDR_TEMP_FILES_FOLDER}:/temp - # networks: - # - osdr-test - # depends_on: - # - rabbitmq - # - mongo + reaction-parser-service: + container_name: reaction-file-parser + image: leanda/reaction-file-parser:${TAG_VERSION-latest} + entrypoint: /bin/bash + environment: + - TZ=EST + - OSDR_LOG_FOLDER=/logs + - OSDR_TEMP_FILES_FOLDER=/temp + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - QUEUE_PREFETCH_SIZE=9 + - EXECUTOR_THREAD_COUNT=3 + command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./wait-for-it.sh mongo:27017 -t 30 -- java -XX:NativeMemoryTracking=summary -jar reaction-parser.jar + volumes: + - ${OSDR_LOG_FOLDER}:/logs + - ${OSDR_TEMP_FILES_FOLDER}:/temp + networks: + - leanda-net + depends_on: + - rabbitmq + - mongo - # crystal-parser-service: - # container_name: crystal-file-parser - # image: docker.your-company.com/sds/crystal-file-parser:${TAG_VERSION-latest} - # entrypoint: /bin/bash - # environment: - # - TZ=EST - # - OSDR_LOG_FOLDER=/logs - # - OSDR_TEMP_FILES_FOLDER=/temp - # - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test - # - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev - # - QUEUE_PREFETCH_SIZE=9 - # - EXECUTOR_THREAD_COUNT=3 - # command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./wait-for-it.sh mongo:27017 -t 30 -- java -XX:NativeMemoryTracking=summary -jar crystal-parser.jar - # volumes: - # - ${OSDR_LOG_FOLDER}:/logs - # - ${OSDR_TEMP_FILES_FOLDER}:/temp - # networks: - # - osdr-test - # depends_on: - # - rabbitmq - # - mongo + crystal-parser-service: + container_name: crystal-file-parser + image: leanda/crystal-file-parser:${TAG_VERSION-latest} + entrypoint: /bin/bash + environment: + - TZ=EST + - OSDR_LOG_FOLDER=/logs + - OSDR_TEMP_FILES_FOLDER=/temp + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - QUEUE_PREFETCH_SIZE=9 + - EXECUTOR_THREAD_COUNT=3 + command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./wait-for-it.sh mongo:27017 -t 30 -- java -XX:NativeMemoryTracking=summary -jar crystal-parser.jar + volumes: + - ${OSDR_LOG_FOLDER}:/logs + - ${OSDR_TEMP_FILES_FOLDER}:/temp + networks: + - leanda-net + depends_on: + - rabbitmq + - mongo - # spectra-parser-service: - # container_name: spectra-file-parser - # image: docker.your-company.com/sds/spectra-file-parser:${TAG_VERSION-latest} - # entrypoint: /bin/bash - # environment: - # - TZ=EST - # - OSDR_LOG_FOLDER=/logs - # - OSDR_TEMP_FILES_FOLDER=/temp - # - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test - # - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev - # - QUEUE_PREFETCH_SIZE=9 - # - EXECUTOR_THREAD_COUNT=3 - # command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./wait-for-it.sh mongo:27017 -t 30 -- java -XX:NativeMemoryTracking=summary -jar spectra-parser.jar - # volumes: - # - ${OSDR_LOG_FOLDER}:/logs - # - ${OSDR_TEMP_FILES_FOLDER}:/temp - # networks: - # - osdr-test - # depends_on: - # - rabbitmq - # - mongo + spectra-parser-service: + container_name: spectra-file-parser + image: leanda/spectra-file-parser:${TAG_VERSION-latest} + entrypoint: /bin/bash + environment: + - TZ=EST + - OSDR_LOG_FOLDER=/logs + - OSDR_TEMP_FILES_FOLDER=/temp + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - QUEUE_PREFETCH_SIZE=9 + - EXECUTOR_THREAD_COUNT=3 + command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./wait-for-it.sh mongo:27017 -t 30 -- java -XX:NativeMemoryTracking=summary -jar spectra-parser.jar + volumes: + - ${OSDR_LOG_FOLDER}:/logs + - ${OSDR_TEMP_FILES_FOLDER}:/temp + networks: + - leanda-net + depends_on: + - rabbitmq + - mongo - chemical-file-parser-service: - container_name: chemical-file-parser-service - image: docker.your-company.com/sds/chemical-file-parser-service:${TAG_VERSION-latest} + chemical-file-parser: + container_name: chemical-file-parser + image: leanda/chemical-file-parser:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - TZ=EST - OSDR_LOG_FOLDER=/logs - OSDR_TEMP_FILES_FOLDER=/temp - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda - QUEUE_PREFETCH_SIZE=9 - EXECUTOR_THREAD_COUNT=3 command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./wait-for-it.sh mongo:27017 -t 30 -- java -jar chemical-parser.jar @@ -219,21 +218,21 @@ services: - ${OSDR_LOG_FOLDER}:/logs - ${OSDR_TEMP_FILES_FOLDER}:/temp networks: - - osdr-test + - leanda-net depends_on: - rabbitmq - mongo - chemical-properties-service: - container_name: chemical-properties-service - image: docker.your-company.com/sds/chemical-properties-service:${TAG_VERSION-latest} + chemical-properties: + container_name: chemical-properties + image: leanda/chemical-properties:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - TZ=EST - OSDR_LOG_FOLDER=/logs - OSDR_TEMP_FILES_FOLDER=/temp - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda - QUEUE_PREFETCH_SIZE=9 - EXECUTOR_THREAD_COUNT=3 command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./wait-for-it.sh mongo:27017 -t 30 -- java -jar sds-chemical-properties-service.jar @@ -241,19 +240,39 @@ services: - ${OSDR_LOG_FOLDER}:/logs - ${OSDR_TEMP_FILES_FOLDER}:/temp networks: - - osdr-test + - leanda-net + depends_on: + - rabbitmq + - mongo + + categories-service: + container_name: categories-service + image: leanda/categories-service:latest + entrypoint: /bin/bash + environment: + - OSDR_LOG_FOLDER=/logs + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda + - OSDR_ES=http://elasticsearch:9200 + - OSDR_LOG_LEVEL=${OSDR_LOG_LEVEL} + command: ./wait-for-it.sh rabbitmq:15672 -t 60 -- ./wait-for-it.sh elasticsearch:9200 -t 60 -- ./Leanda.Categories.Processing + volumes: + - ${OSDR_LOG_FOLDER}:/logs depends_on: + - elasticsearch - rabbitmq - mongo + networks: + - leanda-net - osdr-service-backend: - container_name: osdr-service-backend - image: docker.your-company.com/osdr-service-backend:${TAG_VERSION-latest} + core-backend: + container_name: core-backend + image: leanda/core-backend:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - OSDR_EVENT_STORE=ConnectTo=tcp://admin:changeit@eventstore:1113 - OSDR_REDIS=redis - OSDR_LOG_LEVEL=Error @@ -261,21 +280,21 @@ services: volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test + - leanda-net depends_on: - rabbitmq - eventstore - redis - mongo - osdr-service-frontend: - container_name: osdr-service-frontend - image: docker.your-company.com/osdr-service-frontend:${TAG_VERSION-latest} + core-frontend: + container_name: core-frontend + image: leanda/core-frontend:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - OSDR_EVENT_STORE=ConnectTo=tcp://admin:changeit@eventstore:1113 - OSDR_REDIS=redis - OSDR_LOG_LEVEL=Error @@ -283,68 +302,68 @@ services: volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test + - leanda-net depends_on: - rabbitmq - eventstore - redis - mongo - osdr-service-sagahost: - container_name: osdr-service-sagahost - image: docker.your-company.com/osdr-service-sagahost:${TAG_VERSION-latest} + core-sagahost: + container_name: core-sagahost + image: leanda/core-sagahost:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - OSDR_LOG_LEVEL=Error command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./Sds.Osdr.Domain.SagaHost volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test + - leanda-net depends_on: - rabbitmq - mongo - osdr-service-persistence: - container_name: osdr-service-persistence - image: docker.your-company.com/osdr-service-persistence:${TAG_VERSION-latest} + core-persistence: + container_name: core-persistence + image: leanda/core-persistence:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - OSDR_LOG_LEVEL=Error command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./Sds.Osdr.Persistence volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test + - leanda-net depends_on: - rabbitmq - mongo - osdr-service-web-api: - container_name: osdr-service-web-api - image: docker.your-company.com/osdr-service-web-api:${TAG_VERSION-latest} + core-web-api: + container_name: core-web-api + image: leanda/core-web-api:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - IDENTITY_SERVER_URL=http://keycloak:8080/auth/realms/OSDR - OSDR_REDIS=redis - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - OSDR_EVENT_STORE=ConnectTo=tcp://admin:changeit@eventstore:1113 - #- OSDR_ES=http://elasticsearch:9200 + - OSDR_ES=http://elasticsearch:9200 - SWAGGER_BASEPATH=/osdr/v1 - OSDR_LOG_LEVEL=Error - command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./wait-for-it.sh keycloak:8080 -t 30 -- ./Sds.Osdr.WebApi + command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./wait-for-it.sh keycloak:8080 -t 30 -- ./wait-for-it.sh elasticsearch:9200 -t 60 -- ./Sds.Osdr.WebApi volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test + - leanda-net ports: - "28611:18006" depends_on: @@ -353,24 +372,68 @@ services: - eventstore - redis - mongo + - elasticsearch blob-storage-api: - container_name: osdr-blob-storage-api - image: docker.your-company.com/blob-storage-webapi:${TAG_VERSION-latest} + container_name: blob-storage-api + image: leanda/blob-storage-webapi:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - IDENTITY_SERVER_URL=http://keycloak:8080/auth/realms/OSDR - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - SWAGGER_BASEPATH=/blob/v1 + - OSDR_LOG_LEVEL=${OSDR_LOG_LEVEL} + - MAX_BLOB_SIZE=419430400 command: ./wait-for-it.sh rabbitmq:5672 -t 30 -- ./wait-for-it.sh keycloak:8080 -t 30 -- ./Sds.Storage.Blob.WebApi volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test + - leanda-net ports: - "18006:18006" + depends_on: + - keycloak + - rabbitmq + - mongo + + e2e-tests: + container_name: e2e-tests + image: leanda/e2e-tests:${TAG_VERSION-latest} + entrypoint: /bin/bash + environment: + - IDENTITY_SERVER_URL=http://keycloak:8080/auth/realms/OSDR + - OSDR_BLOB_STORAGE_API=http://blob-storage-api:18006/api/blobs/ + - OSDR_REDIS=redis + - OSDR_LOG_FOLDER=/logs + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_GRID_FS=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda + - OSDR_EVENT_STORE=ConnectTo=tcp://admin:changeit@eventstore:1113 + - OSDR_WEB_API=http://core-web-api:18006 + - OSDR_ES=http://elasticsearch:9200 + command: ./wait-for-it.sh core-web-api:18006 -t 30 -- ./wait-for-it.sh blob-storage-api:18006 -t 30 -- ./wait-for-it.sh keycloak:8080 -t 30 -- dotnet vstest ./Sds.Osdr.EndToEndTests.dll /logger:console;verbosity="normal" + volumes: + - ${OSDR_LOG_FOLDER}:/logs + - /results:/results + networks: + - leanda-net + depends_on: + - core-backend + - core-frontend + - core-sagahost + - core-persistence + - core-web-api + - blob-storage-api + - keycloak + - indexing + - chemical-file-parser + - chemical-properties + - reaction-parser-service + - categories-service + - spectra-parser-service + - crystal-parser-service networks: - osdr-test: + leanda-net: \ No newline at end of file diff --git a/Sds.Osdr.Generic/BackEnd/CommandHandlers/Files/UpdateMetadataCommandHandler.cs b/Sds.Osdr.Generic/BackEnd/CommandHandlers/Files/UpdateMetadataCommandHandler.cs new file mode 100644 index 0000000..369b178 --- /dev/null +++ b/Sds.Osdr.Generic/BackEnd/CommandHandlers/Files/UpdateMetadataCommandHandler.cs @@ -0,0 +1,42 @@ +using CQRSlite.Domain; +using MassTransit; +using Sds.Osdr.Generic.Domain.Events; +using Sds.Osdr.Generic.Domain.Commands.Files; +using Serilog; +using System; +using System.Threading.Tasks; +using Sds.Osdr.Generic.Domain; + +namespace Sds.Osdr.Generic.BackEnd.CommandHandlers.Files +{ + public class UpdateMetadataCommandHandler : IConsumer + { + private readonly ISession session; + + public UpdateMetadataCommandHandler(ISession session) + { + this.session = session ?? throw new ArgumentNullException(nameof(session)); + } + + public async Task Consume(ConsumeContext context) + { + var file = await session.Get(context.Message.Id); + if (file.Version == context.Message.ExpectedVersion) + { + file.UpdateMetadata(context.Message.UserId, context.Message.Metadata); + await session.Commit(); + } + else + { + Log.Error($"Unexpected version for record '{context.Message.Id}', expected version {context.Message.ExpectedVersion}, found {file.Version}"); + await context.Publish(new + { + Id = context.Message.Id, + UserId = context.Message.UserId, + Version = context.Message.ExpectedVersion, + TimeStamp = DateTimeOffset.Now + }); + } + } + } +} diff --git a/Sds.Osdr.Generic/Domain/Aggregates/File.cs b/Sds.Osdr.Generic/Domain/Aggregates/File.cs index 53a7645..c4d4dfd 100644 --- a/Sds.Osdr.Generic/Domain/Aggregates/File.cs +++ b/Sds.Osdr.Generic/Domain/Aggregates/File.cs @@ -1,4 +1,5 @@ using CQRSlite.Domain; +using Sds.Osdr.Domain; using Sds.Osdr.Generic.Domain.Events.Files; using Sds.Osdr.Generic.Domain.ValueObjects; using System; @@ -16,7 +17,8 @@ public enum FileType Tabular, Pdf, WebPage, - Image + Image, + Microscopy } public enum FileStatus @@ -101,6 +103,8 @@ public class File : AggregateRoot public IList Images { get; protected set; } = new List(); + public IEnumerable> Metadata { get; private set; } + /// /// Current file status /// @@ -145,6 +149,13 @@ private void Apply(ImageAdded e) UpdatedDateTime = e.TimeStamp; } + private void Apply(MetadataUpdated e) + { + Metadata = e.Metadata; + UpdatedBy = e.UserId; + UpdatedDateTime = e.TimeStamp; + } + private void Apply(FileNameChanged e) { FileName = e.NewName; @@ -245,5 +256,10 @@ public void GrantAccess(Guid userId, AccessPermissions accessPermissions) { ApplyChange(new PermissionsChanged(Id, userId, accessPermissions)); } + + public void UpdateMetadata(Guid userId, IEnumerable> metadata) + { + ApplyChange(new MetadataUpdated(Id, userId, metadata)); + } } } diff --git a/Sds.Osdr.Generic/Domain/Commands/Files/UpdateMetadata.cs b/Sds.Osdr.Generic/Domain/Commands/Files/UpdateMetadata.cs new file mode 100644 index 0000000..242f7a4 --- /dev/null +++ b/Sds.Osdr.Generic/Domain/Commands/Files/UpdateMetadata.cs @@ -0,0 +1,14 @@ +using Sds.Osdr.Domain; +using System; +using System.Collections.Generic; + +namespace Sds.Osdr.Generic.Domain.Commands.Files +{ + public interface UpdateMetadata + { + IEnumerable> Metadata { get; } + Guid Id { get; } + Guid UserId { get; } + int ExpectedVersion { get; } + } +} diff --git a/Sds.Osdr.Generic/Domain/Events/Files/MetadataPersisted.cs b/Sds.Osdr.Generic/Domain/Events/Files/MetadataPersisted.cs new file mode 100644 index 0000000..3fc202c --- /dev/null +++ b/Sds.Osdr.Generic/Domain/Events/Files/MetadataPersisted.cs @@ -0,0 +1,22 @@ +using Sds.CqrsLite.Events; +using System; + +namespace Sds.Osdr.Generic.Domain.Events.Files +{ + public class MetadataPersisted : IUserEvent + { + public MetadataPersisted(Guid id, Guid userId) + { + Id = id; + UserId = userId; + } + + public Guid Id { get; set; } + + public Guid UserId { get; set; } + + public DateTimeOffset TimeStamp { get; set; } = DateTimeOffset.UtcNow; + + public int Version { get; set; } + } +} diff --git a/Sds.Osdr.Generic/Domain/Events/Files/MetadataUpdated.cs b/Sds.Osdr.Generic/Domain/Events/Files/MetadataUpdated.cs new file mode 100644 index 0000000..da7610e --- /dev/null +++ b/Sds.Osdr.Generic/Domain/Events/Files/MetadataUpdated.cs @@ -0,0 +1,27 @@ +using Sds.CqrsLite.Events; +using Sds.Osdr.Domain; +using System; +using System.Collections.Generic; + +namespace Sds.Osdr.Generic.Domain.Events.Files +{ + public class MetadataUpdated : IUserEvent + { + public readonly IEnumerable> Metadata; + + public MetadataUpdated(Guid id, Guid userId, IEnumerable> metadata) + { + Id = id; + UserId = userId; + Metadata = metadata; + } + + public Guid Id { get; set; } + + public Guid UserId { get; set; } + + public DateTimeOffset TimeStamp { get; set; } = DateTimeOffset.UtcNow; + + public int Version { get; set; } + } +} diff --git a/Sds.Osdr.Generic/Modules/GenericModule.cs b/Sds.Osdr.Generic/Modules/GenericModule.cs index 8dfd25b..07c8ab5 100644 --- a/Sds.Osdr.Generic/Modules/GenericModule.cs +++ b/Sds.Osdr.Generic/Modules/GenericModule.cs @@ -72,6 +72,7 @@ public static void UseInMemoryModule(this IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -103,6 +104,7 @@ public static void UseBackEndModule(this IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -158,6 +160,7 @@ public static void RegisterInMemoryModule(this IBusFactoryConfigurator configura // register backend consumers... configurator.RegisterScopedConsumer(provider, null, c => c.UseCqrsLite()); + configurator.RegisterScopedConsumer(provider, null, c => c.UseCqrsLite()); configurator.RegisterScopedConsumer(provider, null, c => c.UseCqrsLite()); configurator.RegisterScopedConsumer(provider, null, c => c.UseCqrsLite()); configurator.RegisterScopedConsumer(provider, null, c => c.UseCqrsLite()); @@ -184,6 +187,7 @@ public static void RegisterBackEndModule(this IRabbitMqBusFactoryConfigurator co { // register backend consumers... configurator.RegisterScopedConsumer(host, provider, endpointConfigurator, c => c.UseCqrsLite()); + configurator.RegisterScopedConsumer(host, provider, endpointConfigurator, c => c.UseCqrsLite()); configurator.RegisterScopedConsumer(host, provider, endpointConfigurator, c => c.UseCqrsLite()); configurator.RegisterScopedConsumer(host, provider, endpointConfigurator, c => c.UseCqrsLite()); configurator.RegisterScopedConsumer(host, provider, endpointConfigurator, c => c.UseCqrsLite()); diff --git a/Sds.Osdr.Generic/Persistence/EventHandlers/Files/FileEventHandlers.cs b/Sds.Osdr.Generic/Persistence/EventHandlers/Files/FileEventHandlers.cs index 7bdf311..fc8991c 100644 --- a/Sds.Osdr.Generic/Persistence/EventHandlers/Files/FileEventHandlers.cs +++ b/Sds.Osdr.Generic/Persistence/EventHandlers/Files/FileEventHandlers.cs @@ -17,7 +17,8 @@ public class FileEventHandlers : IConsumer, IConsumer, IConsumer, IConsumer, - IConsumer + IConsumer, + IConsumer { private readonly IMongoDatabase database; private IMongoCollection Files { get { return database.GetCollection("Files"); } } @@ -198,5 +199,27 @@ await context.Publish(new TimeStamp = DateTimeOffset.UtcNow }); } + + public async Task Consume(ConsumeContext context) + { + var filter = new BsonDocument("_id", context.Message.Id).Add("Version", context.Message.Version - 1); + var update = Builders.Update + .Set("Properties", new { Metadata = context.Message.Metadata }.ToBsonDocument()) + .Set("UpdatedBy", context.Message.UserId) + .Set("UpdatedDateTime", context.Message.TimeStamp.UtcDateTime) + .Set("Version", context.Message.Version); + + var document = await Files.FindOneAndUpdateAsync(filter, update); + + if (document == null) + throw new ConcurrencyException(context.Message.Id); + + await context.Publish(new + { + Id = context.Message.Id, + UserId = context.Message.UserId, + TimeStamp = DateTimeOffset.UtcNow + }); + } } } diff --git a/Sds.Osdr.Generic/Sds.Osdr.Generic.csproj b/Sds.Osdr.Generic/Sds.Osdr.Generic.csproj index 93635e7..067127d 100644 --- a/Sds.Osdr.Generic/Sds.Osdr.Generic.csproj +++ b/Sds.Osdr.Generic/Sds.Osdr.Generic.csproj @@ -2,14 +2,17 @@ netstandard2.0 Sds.Osdr.Generic - 0.13.1 Science Data Software Generic processing domain module false First release - Copyright 2017 (c) Science Data Software. All rights reserved. + domain, generic, module Debug;Release;Dev + true + https://opensource.org/licenses/MIT + 0.13.1 + Open Science Data Repository diff --git a/Sds.Osdr.Infrastructure/Sds.Osdr.Infrastructure.csproj b/Sds.Osdr.Infrastructure/Sds.Osdr.Infrastructure.csproj index c02d28d..19dac1a 100644 --- a/Sds.Osdr.Infrastructure/Sds.Osdr.Infrastructure.csproj +++ b/Sds.Osdr.Infrastructure/Sds.Osdr.Infrastructure.csproj @@ -3,11 +3,16 @@ netstandard2.0 Debug;Release;Dev + Open Science Data Repository + true + Science Data Software + Science Data Software + https://opensource.org/licenses/MIT - + diff --git a/Sds.Osdr.IntegrationTests/.env.jenkins b/Sds.Osdr.IntegrationTests/.env.jenkins deleted file mode 100644 index 3d8dd6c..0000000 --- a/Sds.Osdr.IntegrationTests/.env.jenkins +++ /dev/null @@ -1,2 +0,0 @@ -OSDR_LOG_FOLDER=/logs -OSDR_LOG_LEVEL=Error \ No newline at end of file diff --git a/Sds.Osdr.IntegrationTests/.env.travis-ci b/Sds.Osdr.IntegrationTests/.env.travis-ci new file mode 100644 index 0000000..6167394 --- /dev/null +++ b/Sds.Osdr.IntegrationTests/.env.travis-ci @@ -0,0 +1,3 @@ +OSDR_LOG_FOLDER=/logs +OSDR_LOG_LEVEL=Error +TAG_VERSION=ci \ No newline at end of file diff --git a/Sds.Osdr.IntegrationTests/Dockerfile b/Sds.Osdr.IntegrationTests/Dockerfile index 3ebeeb1..748581c 100644 --- a/Sds.Osdr.IntegrationTests/Dockerfile +++ b/Sds.Osdr.IntegrationTests/Dockerfile @@ -4,6 +4,8 @@ ARG RID=linux-x64 WORKDIR /build +COPY Leanda.Microscopy/Leanda.Microscopy.csproj Leanda.Microscopy/ +COPY Leanda.Categories/Leanda.CategoryTree.csproj Leanda.Categories/ COPY Sds.Osdr.Chemicals/Sds.Osdr.Chemicals.csproj Sds.Osdr.Chemicals/ COPY Sds.Osdr.Crystals/Sds.Osdr.Crystals.csproj Sds.Osdr.Crystals/ COPY Sds.Osdr.Domain/Sds.Osdr.Domain.csproj Sds.Osdr.Domain/ @@ -23,6 +25,8 @@ COPY Nuget.config . RUN dotnet restore --configfile Nuget.config Sds.Osdr.IntegrationTests/Sds.Osdr.IntegrationTests.csproj +COPY Leanda.Microscopy Leanda.Microscopy +COPY Leanda.Categories Leanda.Categories COPY Sds.Osdr.Chemicals Sds.Osdr.Chemicals COPY Sds.Osdr.Crystals Sds.Osdr.Crystals COPY Sds.Osdr.Domain Sds.Osdr.Domain diff --git a/Sds.Osdr.IntegrationTests/FluentAssersions/Files/FileAssersions.cs b/Sds.Osdr.IntegrationTests/FluentAssersions/Files/FileAssersions.cs index 990c426..a64952e 100644 --- a/Sds.Osdr.IntegrationTests/FluentAssersions/Files/FileAssersions.cs +++ b/Sds.Osdr.IntegrationTests/FluentAssersions/Files/FileAssersions.cs @@ -18,7 +18,7 @@ public static class FileAssersionsExtensions { public static void EntityShouldBeEquivalentTo(this GenericDictionaryAssertions assertions, File file) { - assertions.Subject.ShouldAllBeEquivalentTo(new Dictionary() + assertions.Subject.Should().BeEquivalentTo(new Dictionary() { { "_id", file.Id}, { "Blob", new Dictionary() { @@ -50,7 +50,7 @@ public static void EntityShouldBeEquivalentTo(this GenericDictionaryAssertions assertions, File file) { - assertions.Subject.ShouldAllBeEquivalentTo(new Dictionary() + assertions.Subject.Should().BeEquivalentTo(new Dictionary() { { "_id", file.Id}, { "Type", "File" }, @@ -83,7 +83,7 @@ public static void NodeShouldBeEquivalentTo(this GenericDictionaryAssertions assertions, OfficeFile file) { - assertions.Subject.ShouldAllBeEquivalentTo(new Dictionary() + assertions.Subject.Should().BeEquivalentTo(new Dictionary() { { "_id", file.Id}, { "Type", "File" }, @@ -129,7 +129,7 @@ public static void OfficeNodeShouldBeEquivalentTo(this GenericDictionaryAssertio public static void FileViewShouldBeEquivalentTo(this GenericDictionaryAssertions assertions, OfficeFile file) { - assertions.Subject.ShouldAllBeEquivalentTo(new Dictionary + assertions.Subject.Should().BeEquivalentTo(new Dictionary { { "_id", file.Id}, { "Blob", new Dictionary() { @@ -213,7 +213,7 @@ public static void WebPageEntityShouldBeEquivalentTo(this GenericDictionaryAsser })); } - assertions.Subject.ShouldAllBeEquivalentTo(expected); + assertions.Subject.Should().BeEquivalentTo(expected); } @@ -258,7 +258,7 @@ public static void WebNodeShouldBeEquivalentTo(this GenericDictionaryAssertions< })); } - assertions.Subject.ShouldAllBeEquivalentTo(expected); + assertions.Subject.Should().BeEquivalentTo(expected); } public static void PdfEntityShouldBeEquivalentTo(this GenericDictionaryAssertions assertions, PdfFile file) @@ -297,7 +297,7 @@ public static void PdfEntityShouldBeEquivalentTo(this GenericDictionaryAssertion })); } - assertions.Subject.ShouldAllBeEquivalentTo(expected); + assertions.Subject.Should().BeEquivalentTo(expected); } public static void TabularEntityShouldBeEquivalentTo(this GenericDictionaryAssertions assertions, TabularFile file) @@ -336,7 +336,7 @@ public static void TabularEntityShouldBeEquivalentTo(this GenericDictionaryAsser })); } - assertions.Subject.ShouldAllBeEquivalentTo(expected); + assertions.Subject.Should().BeEquivalentTo(expected); } public static void ModelNodeShouldBeEquivalentTo(this GenericDictionaryAssertions assertions, Model model) @@ -372,7 +372,7 @@ public static void ModelNodeShouldBeEquivalentTo(this GenericDictionaryAssertion { "Bucket", i.Bucket } })); } - assertions.Subject.ShouldAllBeEquivalentTo(expected); + assertions.Subject.Should().BeEquivalentTo(expected); } public struct Fingerprint @@ -464,8 +464,7 @@ public static void ModelEntityShouldBeEquivalentTo(this GenericDictionaryAsserti Log.Information($"Expected: {JsonConvert.SerializeObject(expected)}"); Log.Information($"Subject: {JsonConvert.SerializeObject(assertions.Subject)}"); - expected.ShouldAllBeEquivalentTo(assertions.Subject); - //assertions.Subject.ShouldAllBeEquivalentTo(expected); + expected.Should().BeEquivalentTo(assertions.Subject); } } } diff --git a/Sds.Osdr.IntegrationTests/FluentAssersions/Files/MicroscopyFileAssersions.cs b/Sds.Osdr.IntegrationTests/FluentAssersions/Files/MicroscopyFileAssersions.cs new file mode 100644 index 0000000..3382317 --- /dev/null +++ b/Sds.Osdr.IntegrationTests/FluentAssersions/Files/MicroscopyFileAssersions.cs @@ -0,0 +1,51 @@ +using FluentAssertions; +using FluentAssertions.Collections; +using Leanda.Microscopy.Domain; +using Sds.Osdr.Generic.Domain; +using Sds.Osdr.Generic.Extensions; +using System.Collections.Generic; +using System.Linq; + +namespace Sds.Osdr.IntegrationTests.FluentAssersions +{ + public static class MicroscopyFileAssersionsExtensions + { + public static void EntityShouldBeEquivalentTo(this GenericDictionaryAssertions assertions, MicroscopyFile file) + { + assertions.Subject.Should().BeEquivalentTo(new Dictionary + { + { "_id", file.Id}, + { "Blob", new Dictionary() { + { "_id", file.BlobId}, + { "Bucket", file.Bucket }, + { "Length", file.Length }, + { "Md5", file.Md5 } + } }, + { "SubType", FileType.Microscopy.ToString() }, + { "OwnedBy", file.OwnedBy }, + { "CreatedBy", file.CreatedBy }, + { "CreatedDateTime", file.CreatedDateTime.UtcDateTime}, + { "UpdatedBy", file.UpdatedBy }, + { "UpdatedDateTime", file.UpdatedDateTime.UtcDateTime}, + { "ParentId", file.ParentId }, + { "Name", file.FileName }, + { "Status", file.Status.ToString() }, + { "Version", file.Version }, + { "Images", file.Images.Select(i => new Dictionary { + { "_id", i.Id }, + { "Bucket", file.Bucket }, + { "Height", i.Height }, + { "Width", i.Height }, + { "MimeType", i.MimeType }, + { "Scale", i.GetScale() } + })}, + { "Properties", new Dictionary() { + { "BioMetadata", file.BioMetadata.Select(i => new Dictionary { + { "Name", i.Name }, + { "Value", i.Value } + }) + }}} + }); + } + } +} diff --git a/Sds.Osdr.IntegrationTests/FluentAssersions/Files/OfficeFileAssersions.cs b/Sds.Osdr.IntegrationTests/FluentAssersions/Files/OfficeFileAssersions.cs index 8aec8a4..d58db44 100644 --- a/Sds.Osdr.IntegrationTests/FluentAssersions/Files/OfficeFileAssersions.cs +++ b/Sds.Osdr.IntegrationTests/FluentAssersions/Files/OfficeFileAssersions.cs @@ -12,7 +12,7 @@ public static class OfficeFileAssersionsExtensions { public static void EntityShouldBeEquivalentTo(this GenericDictionaryAssertions assertions, OfficeFile file) { - assertions.Subject.ShouldAllBeEquivalentTo(new Dictionary + assertions.Subject.Should().BeEquivalentTo(new Dictionary { { "_id", file.Id}, { "Blob", new Dictionary() { diff --git a/Sds.Osdr.IntegrationTests/FluentAssersions/Files/RecordsFileAssersions.cs b/Sds.Osdr.IntegrationTests/FluentAssersions/Files/RecordsFileAssersions.cs index 184a9b4..b2f4aa1 100644 --- a/Sds.Osdr.IntegrationTests/FluentAssersions/Files/RecordsFileAssersions.cs +++ b/Sds.Osdr.IntegrationTests/FluentAssersions/Files/RecordsFileAssersions.cs @@ -68,7 +68,7 @@ public static void EntityShouldBeEquivalentTo(this GenericDictionaryAssertions assertions, RecordsFile.Domain.RecordsFile file) @@ -114,7 +114,7 @@ public static void NodeShouldBeEquivalentTo(this GenericDictionaryAssertions assertions, Newtonsoft.Json.Linq.JObject record) { @@ -157,7 +157,7 @@ public static void NodeShouldBeEquivalentToJson(this GenericCollectionAssertions var dAssertion = convertToDictionary((Newtonsoft.Json.Linq.JToken)assertions.Subject); var dRecord = convertToDictionary(record); - dAssertion.ShouldAllBeEquivalentTo(dRecord); + dAssertion.Should().BeEquivalentTo(dRecord); } public static void ContainsJson(this GenericCollectionAssertions asserions, Dictionary file) { @@ -219,6 +219,7 @@ public static void ContainsJson(this GenericCollectionAssertions asserio file = file.Replace("*EXIST*", "'*no_valid_field*'"); ContainsJson(asserions, JObject.Parse(file), ignoreFields); } + public static void ContainsJson(this GenericDictionaryAssertions assertions, string file, List ignoreFields = null) { file = file.Replace("*EXIST*", "'*no_valid_field*'"); diff --git a/Sds.Osdr.IntegrationTests/FluentAssersions/Folders/FolderAssersions.cs b/Sds.Osdr.IntegrationTests/FluentAssersions/Folders/FolderAssersions.cs index 54aa3b7..b2677c4 100644 --- a/Sds.Osdr.IntegrationTests/FluentAssersions/Folders/FolderAssersions.cs +++ b/Sds.Osdr.IntegrationTests/FluentAssersions/Folders/FolderAssersions.cs @@ -11,7 +11,7 @@ public static void EntityShouldBeEquivalentTo(this GenericDictionaryAssertions() + assertions.Subject.Should().BeEquivalentTo(new Dictionary() { { "_id", folder.Id}, { "CreatedBy", folder.CreatedBy }, @@ -31,7 +31,7 @@ public static void NodeShouldBeEquivalentTo(this GenericDictionaryAssertions() + assertions.Subject.Should().BeEquivalentTo(new Dictionary() { { "_id", folder.Id}, { "Type", "Folder" }, diff --git a/Sds.Osdr.IntegrationTests/FluentAssersions/Records/SubstanceAssersions.cs b/Sds.Osdr.IntegrationTests/FluentAssersions/Records/SubstanceAssersions.cs index 6ca7213..c355788 100644 --- a/Sds.Osdr.IntegrationTests/FluentAssersions/Records/SubstanceAssersions.cs +++ b/Sds.Osdr.IntegrationTests/FluentAssersions/Records/SubstanceAssersions.cs @@ -18,7 +18,7 @@ public static void EntityShouldBeEquivalentTo(this GenericDictionaryAssertions() { { "_id", record.BlobId }, @@ -74,7 +74,7 @@ public static void EntityShouldBeEquivalentTo(this GenericDictionaryAssertions assertions, Record record) @@ -86,7 +86,7 @@ public static void NodeShouldBeEquivalentTo(this GenericDictionaryAssertions() { { "_id", record.BlobId }, { "Bucket", record.Bucket }, @@ -115,7 +115,7 @@ public static void NodeShouldBeEquivalentTo(this GenericDictionaryAssertions assertions, InvalidRecord record) @@ -138,7 +138,7 @@ public static void EntityShouldBeEquivalentTo(this GenericDictionaryAssertions assertions, InvalidRecord record) @@ -150,7 +150,7 @@ public static void NodeShouldBeEquivalentTo(this GenericDictionaryAssertions() + assertions.Subject.Should().BeEquivalentTo(new Dictionary() { { "_id", user.Id}, { "CreatedBy", user.CreatedBy }, @@ -32,7 +32,7 @@ public static void NodeShouldBeEquivalentTo(this GenericDictionaryAssertions() + assertions.Subject.Should().BeEquivalentTo(new Dictionary() { { "_id", user.Id}, { "Type", "User" }, diff --git a/Sds.Osdr.IntegrationTests/KeyCloak/KeyCloakClient.cs b/Sds.Osdr.IntegrationTests/KeyCloak/KeyCloakClient.cs index cd7bd3d..74a69c2 100644 --- a/Sds.Osdr.IntegrationTests/KeyCloak/KeyCloakClient.cs +++ b/Sds.Osdr.IntegrationTests/KeyCloak/KeyCloakClient.cs @@ -1,8 +1,12 @@ -using Newtonsoft.Json; +using Flurl; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using Serilog; using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; +using System.Text; using System.Threading.Tasks; namespace Sds.Osdr.IntegrationTests @@ -24,12 +28,17 @@ public class UserInfo public class KeyCloalClient : HttpClient { - public Uri Authority { get; } = new Uri("http://keycloak:8080/auth/realms/OSDR/"); + public string Authority { get; } public string ClientId { get; } = "osdr_webapi"; public string ClientSecret { get; } = "52f5b3fc-2167-40b1-9508-6bb3091782bd"; public KeyCloalClient() : base() { + var configuration = new ConfigurationBuilder() + .AddJsonFile($"appsettings.json", false, true) + .AddEnvironmentVariables() + .Build(); + Authority = Environment.ExpandEnvironmentVariables(configuration["KeyCloak:Authority"]); } public async Task GetToken(string username, string password) @@ -39,11 +48,18 @@ public async Task GetToken(string username, string password) nvc.Add(new KeyValuePair("password", password)); nvc.Add(new KeyValuePair("grant_type", "password")); - var request = new HttpRequestMessage(HttpMethod.Post, new Uri(Authority, "protocol/openid-connect/token")) { Content = new FormUrlEncodedContent(nvc) }; + Log.Debug($"GetToken({username}, {password})"); + Log.Debug($"URL: {Url.Combine(Authority, "protocol/openid-connect/token")}"); - DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{ClientId}:{ClientSecret}"))); + var request = new HttpRequestMessage(HttpMethod.Post, new Uri(Url.Combine(Authority, "protocol/openid-connect/token"))) { Content = new FormUrlEncodedContent(nvc) }; - var json = await SendAsync(request).Result.Content.ReadAsStringAsync(); + DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{ClientId}:{ClientSecret}"))); + + var response = await SendAsync(request); + + Log.Debug($"Response: {response})"); + + var json = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(json); } @@ -53,11 +69,13 @@ public async Task GetClientToken(string clientId, string secret) var nvc = new List>(); nvc.Add(new KeyValuePair("grant_type", "client_credentials")); - var request = new HttpRequestMessage(HttpMethod.Post, new Uri(Authority, "protocol/openid-connect/token")) { Content = new FormUrlEncodedContent(nvc) }; + var request = new HttpRequestMessage(HttpMethod.Post, new Uri(Url.Combine(Authority, "protocol/openid-connect/token"))) { Content = new FormUrlEncodedContent(nvc) }; + + DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{clientId}:{secret}"))); - DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{clientId}:{secret}"))); + var response = await SendAsync(request); - var json = await SendAsync(request).Result.Content.ReadAsStringAsync(); + var json = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(json); } @@ -73,9 +91,9 @@ public async Task GetUserInfo(string token) { DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - var userInfoResponse = await GetAsync(new Uri(Authority, "protocol/openid-connect/userinfo")); + var response = await GetAsync(new Uri(Url.Combine(Authority, "protocol/openid-connect/userinfo"))); - var json = await userInfoResponse.Content.ReadAsStringAsync(); + var json = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(json); } diff --git a/Sds.Osdr.IntegrationTests/Moq/CategoryEntities.cs b/Sds.Osdr.IntegrationTests/Moq/CategoryEntities.cs new file mode 100644 index 0000000..26e32c1 --- /dev/null +++ b/Sds.Osdr.IntegrationTests/Moq/CategoryEntities.cs @@ -0,0 +1,62 @@ +using Leanda.Categories.Domain.Commands; +using MassTransit; +using Nest; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.ChemicalFileParser.Domain; +using Sds.ChemicalFileParser.Domain.Commands; +using Sds.Domain; +using Sds.Storage.Blob.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sds.Osdr.IntegrationTests.Moq +{ + public class CategoryEntities : IConsumer, + IConsumer + { + private readonly IElasticClient _elasticClient; + + public CategoryEntities(IElasticClient elasticClient) + { + _elasticClient = elasticClient; + } + + public async Task Consume(ConsumeContext context) + { + var node = new { _id = context.Message.EntityId, Name = "Moq Name of Node" }; + var insertDocument = new { CategoriesIds = context.Message.CategoriesIds.Distinct(), Node = node }; + + var status = await _elasticClient.IndexAsync(insertDocument, + i => i.Index("categories").Type("category")); + } + + public async Task Consume(ConsumeContext context) + { + var result = _elasticClient.Search(s => s + .Index("categories") + .Type("category") + .Query(q => q.QueryString(qs => qs.Query(context.Message.EntityId.ToString())))); + + foreach (var hit in result.Hits) + { + JObject hitObject = JsonConvert.DeserializeObject(hit.Source.ToString()); + IEnumerable categoriesIds = hitObject.Value("CategoriesIds").Select(x => x.ToString()); + categoriesIds = categoriesIds.Where(x => !context.Message.CategoriesIds.Select(z => z.ToString()).Contains(x)); + + if (categoriesIds.Any()) + { + var indexDocument = new { CategoriesIds = categoriesIds.Distinct() }; + await _elasticClient.UpdateAsync(hit.Id, + i => i.Doc(indexDocument).Index("categories").Type("category")); + } + else + { + await _elasticClient.DeleteAsync(new DeleteRequest("categories", "category", hit.Id)); + } + } + } + } +} diff --git a/Sds.Osdr.IntegrationTests/Moq/Imaging.cs b/Sds.Osdr.IntegrationTests/Moq/Imaging.cs index 4da9f11..2f25c6f 100644 --- a/Sds.Osdr.IntegrationTests/Moq/Imaging.cs +++ b/Sds.Osdr.IntegrationTests/Moq/Imaging.cs @@ -81,7 +81,6 @@ await context.Publish(new MimeType = "image/svg+xml" }; - //await _eventPublisher.Publish(new ImageGenerated(Guid.NewGuid(), context.Message.Bucket, Guid.NewGuid(), image, context.Message.CorrelationId, context.Message.UserId)); await context.Publish(new { Id = context.Message.Id, diff --git a/Sds.Osdr.IntegrationTests/Moq/MicroscopyMetadata.cs b/Sds.Osdr.IntegrationTests/Moq/MicroscopyMetadata.cs new file mode 100644 index 0000000..72e02d5 --- /dev/null +++ b/Sds.Osdr.IntegrationTests/Moq/MicroscopyMetadata.cs @@ -0,0 +1,38 @@ +using Leanda.Microscopy.Metadata.Domain.Commands; +using Leanda.Microscopy.Metadata.Domain.Events; +using MassTransit; +using Sds.Storage.Blob.Core; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Sds.Osdr.IntegrationTests.Moq +{ + public class MicroscopyMetadata : IConsumer + { + private readonly IBlobStorage _blobStorage; + + public MicroscopyMetadata(IBlobStorage blobStorage) + { + _blobStorage = blobStorage ?? throw new ArgumentNullException(nameof(blobStorage)); ; + } + + public async Task Consume(ConsumeContext context) + { + await context.Publish(new + { + Id = context.Message.Id, + CorrelationId = context.Message.CorrelationId, + UserId = context.Message.UserId, + TimeStamp = DateTimeOffset.UtcNow, + Metadata = new Dictionary() + { + { "Experimenter", "Experimenter name" }, + { "Experimenter Group", "Experimenter group name" }, + { "Project", "Project name" }, + { "Experiment", "Experiment" } + } + }); + } + } +} diff --git a/Sds.Osdr.IntegrationTests/Moq/OfficeProcessor.cs b/Sds.Osdr.IntegrationTests/Moq/OfficeProcessor.cs index 9c3ccbf..0bd893a 100644 --- a/Sds.Osdr.IntegrationTests/Moq/OfficeProcessor.cs +++ b/Sds.Osdr.IntegrationTests/Moq/OfficeProcessor.cs @@ -47,7 +47,7 @@ public async Task Consume(ConsumeContext context) var meta = new List() { new Property( "CreatedBy", "John Doe"), - new Property("CreatedDateTime", DateTimeOffset.UtcNow) + new Property("CreatedDateTime", DateTimeOffset.UtcNow.ToString()) }; await context.Publish(new diff --git a/Sds.Osdr.IntegrationTests/OsdrTestHarness.cs b/Sds.Osdr.IntegrationTests/OsdrTestHarness.cs index 3e0158f..122b3ae 100644 --- a/Sds.Osdr.IntegrationTests/OsdrTestHarness.cs +++ b/Sds.Osdr.IntegrationTests/OsdrTestHarness.cs @@ -13,6 +13,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using MongoDB.Driver; +using Nest; +using Newtonsoft.Json.Linq; using Sds.CqrsLite.EventStore; using Sds.MassTransit.Extensions; using Sds.MassTransit.Observers; @@ -27,6 +29,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Threading; using System.Threading.Tasks; namespace Sds.Osdr.IntegrationTests @@ -48,7 +51,7 @@ public class OsdrTestHarness : IDisposable public IEventStore CqrsEventStore { get { return _serviceProvider.GetService(); } } public EventStore.IEventStore EventStore { get { return _serviceProvider.GetService(); } } public IMongoDatabase MongoDb { get { return _serviceProvider.GetService(); } } - + public IElasticClient ElasticClient { get { return _serviceProvider.GetService(); } } public IBusControl BusControl { get { return _serviceProvider.GetService(); } } private IDictionary> ProcessedRecords { get; } = new Dictionary>(); @@ -93,6 +96,10 @@ public OsdrTestHarness() services.AddSingleton(new MongoClient(mongoUrl)); services.AddScoped(service => service.GetService().GetDatabase(mongoUrl.DatabaseName)); + var settings = new ConnectionSettings(new Uri(Environment.ExpandEnvironmentVariables(configuration["ElasticSearch:ConnectionString"]))); + settings.DefaultFieldNameInferrer(f => f); + services.AddSingleton(new ElasticClient(settings)); + services.AddTransient(x => { var blobStorageUrl = new MongoUrl(Environment.ExpandEnvironmentVariables(configuration["GridFs:ConnectionString"])); @@ -103,9 +110,7 @@ public OsdrTestHarness() services.AddSingleton(); - var integrationTestsAssembly = Assembly.LoadFrom("Sds.Osdr.IntegrationTests.dll"); - - services.AddAllConsumers(integrationTestsAssembly); + OnInit(services); services.AddSingleton(container => Bus.Factory.CreateUsingRabbitMq(x => { @@ -113,22 +118,7 @@ public OsdrTestHarness() IRabbitMqHost host = x.Host(new Uri(Environment.ExpandEnvironmentVariables(mtSettings.ConnectionString)), h => { }); - x.RegisterConsumers(host, container, e => - { - e.UseDelayedRedelivery(r => - { - r.Interval(mtSettings.RedeliveryCount, TimeSpan.FromMilliseconds(mtSettings.RedeliveryInterval)); - r.Handle(); - }); - e.UseMessageRetry(r => - { - r.Interval(mtSettings.RetryCount, TimeSpan.FromMilliseconds(mtSettings.RetryInterval)); - r.Handle(); - }); - - e.PrefetchCount = mtSettings.PrefetchCount; - e.UseInMemoryOutbox(); - }, integrationTestsAssembly); + OnBusCreation(x, host, container); x.ReceiveEndpoint(host, "processing_fault_queue", e => { @@ -201,6 +191,24 @@ public OsdrTestHarness() e.Handler(context => { Received.Add(context); return Task.CompletedTask; }); e.Handler(context => { Received.Add(context); return Task.CompletedTask; }); + + e.Handler(context => { Received.Add(context); return Task.CompletedTask; }); + + e.Handler(context => { Received.Add(context); return Task.CompletedTask; }); + + e.Handler(context => { Received.Add(context); return Task.CompletedTask; }); + + e.Handler(context => { Received.Add(context); return Task.CompletedTask; }); + + e.Handler(context => { Received.Add(context); return Task.CompletedTask; }); + + e.Handler(context => { Received.Add(context); return Task.CompletedTask; }); + + e.Handler(context => { Received.Add(context); return Task.CompletedTask; }); + + e.Handler(context => { Received.Add(context); return Task.CompletedTask; }); + + e.Handler(context => { Received.Add(context); return Task.CompletedTask; }); }); x.UseConcurrencyLimit(mtSettings.ConcurrencyLimit); @@ -225,6 +233,37 @@ public OsdrTestHarness() ); } + protected virtual void OnInit(IServiceCollection services) + { + var integrationTestsAssembly = Assembly.LoadFrom("Sds.Osdr.IntegrationTests.dll"); + + services.AddAllConsumers(integrationTestsAssembly); + } + + protected virtual void OnBusCreation(IRabbitMqBusFactoryConfigurator config, IRabbitMqHost host, IServiceProvider container) + { + var integrationTestsAssembly = Assembly.LoadFrom("Sds.Osdr.IntegrationTests.dll"); + + var mtSettings = container.GetService>().Value; + + config.RegisterConsumers(host, container, e => + { + e.UseDelayedRedelivery(r => + { + r.Interval(mtSettings.RedeliveryCount, TimeSpan.FromMilliseconds(mtSettings.RedeliveryInterval)); + r.Handle(); + }); + e.UseMessageRetry(r => + { + r.Interval(mtSettings.RetryCount, TimeSpan.FromMilliseconds(mtSettings.RetryInterval)); + r.Handle(); + }); + + e.PrefetchCount = mtSettings.PrefetchCount; + e.UseInMemoryOutbox(); + }, integrationTestsAssembly); + } + protected void Seed() { var john = keycloak.GetUserInfo("john", "qqq123").Result; @@ -316,6 +355,44 @@ public IEnumerable GetDependentFilesExcept(Guid parentId, params FileType[ .Select(f => f.Id); } + public void WaitWhileCategoryIndexed(string categoryId) + { + var elasticSearchNodes = new List(); + for (int i = 0; i < 100; i++) + { + var result = ElasticClient.Search(s => s + .Index("categories") + .Type("category") + .Query(q => q.QueryString(qs => qs.Query(categoryId)))); + var hits = result.Hits; + if (hits.Any()) + { + return; + } + Thread.Sleep(100); + } + return; + } + + public void WaitWhileCategoryDeleted(string categoryId) + { + var elasticSearchNodes = new List(); + for (int i = 0; i < 100; i++) + { + var result = ElasticClient.Search(s => s + .Index("categories") + .Type("category") + .Query(q => q.QueryString(qs => qs.Query(categoryId)))); + var hits = result.Hits.FirstOrDefault(); + if (hits == null) + { + return; + } + Thread.Sleep(100); + } + return; + } + public virtual void Dispose() { var busControl = _serviceProvider.GetRequiredService(); diff --git a/Sds.Osdr.IntegrationTests/OsdrTestHarnessExtensions.cs b/Sds.Osdr.IntegrationTests/OsdrTestHarnessExtensions.cs index 5bf29ea..872ee01 100644 --- a/Sds.Osdr.IntegrationTests/OsdrTestHarnessExtensions.cs +++ b/Sds.Osdr.IntegrationTests/OsdrTestHarnessExtensions.cs @@ -1,4 +1,5 @@ -using MassTransit; +using Leanda.Categories.Domain.Events; +using MassTransit; using Newtonsoft.Json; using Sds.MassTransit.Extensions; using Sds.Osdr.Generic.Domain.Commands.Folders; @@ -494,6 +495,32 @@ public static void WaitWhileFileShared(this OsdrTestHarness harness, Guid id) } } + public static void WaitWhileFileRenamed(this OsdrTestHarness harness, Guid id) + { + if (!harness.Received.Select(m => m.Context.Message.Id == id).Any()) + { + throw new TimeoutException(); + } + + if (!harness.Received.Select(m => m.Context.Message.Id == id).Any()) + { + throw new TimeoutException(); + } + } + + public static void WaitWhileFileMoved(this OsdrTestHarness harness, Guid id) + { + if (!harness.Received.Select(m => m.Context.Message.Id == id).Any()) + { + throw new TimeoutException(); + } + + if (!harness.Received.Select(m => m.Context.Message.Id == id).Any()) + { + throw new TimeoutException(); + } + } + //public static void WaitWhileFolderShared(this BusTestHarness harness, Guid id) //{ // if (!harness.Published.Select(m => m.Context.Message.Id == id).Any()) @@ -610,5 +637,45 @@ public static void WaitWhileBlobLoaded(this OsdrTestHarness harness, Guid blobId throw new TimeoutException(); } } + + public static void WaitMetadataUpdated(this OsdrTestHarness harness, Guid id) + { + if (!harness.Received.Select(m => m.Context.Message.Id == id).Any()) + { + throw new TimeoutException(); + } + } + + public static void WaitWhileCategoryTreePersisted(this OsdrTestHarness harness, Guid id) + { + if (!harness.Received.Select(m => m.Context.Message.Id == id).Any()) + { + throw new TimeoutException(); + } + } + + public static void WaitWhileCategoryTreeUpdatedPersisted(this OsdrTestHarness harness, Guid id) + { + if (!harness.Received.Select(m => m.Context.Message.Id == id).Any()) + { + throw new TimeoutException(); + } + } + + public static void WaitWhileCategoryTreeDeletePersisted(this OsdrTestHarness harness, Guid id) + { + if (!harness.Received.Select(m => m.Context.Message.Id == id).Any()) + { + throw new TimeoutException(); + } + } + + public static void WaitWhileCategoryTreeNodeDeletePersisted(this OsdrTestHarness harness, Guid nodeId) + { + if (!harness.Received.Select(m => m.Context.Message.NodeId == nodeId).Any()) + { + throw new TimeoutException(); + } + } } } diff --git a/Sds.Osdr.IntegrationTests/Resources/Nikon_BF007.nd2 b/Sds.Osdr.IntegrationTests/Resources/Nikon_BF007.nd2 new file mode 100644 index 0000000..d0abcf9 Binary files /dev/null and b/Sds.Osdr.IntegrationTests/Resources/Nikon_BF007.nd2 differ diff --git a/Sds.Osdr.IntegrationTests/Sds.Osdr.IntegrationTests.csproj b/Sds.Osdr.IntegrationTests/Sds.Osdr.IntegrationTests.csproj index cccb845..24f55bc 100644 --- a/Sds.Osdr.IntegrationTests/Sds.Osdr.IntegrationTests.csproj +++ b/Sds.Osdr.IntegrationTests/Sds.Osdr.IntegrationTests.csproj @@ -10,12 +10,15 @@ + + - - + + + @@ -23,6 +26,7 @@ + @@ -30,9 +34,11 @@ all runtime; build; native; contentfiles; analyzers - + + + @@ -207,6 +213,9 @@ Always + + Always + Always diff --git a/Sds.Osdr.IntegrationTests/Tests/Crystals/InvalidCifProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/Crystals/InvalidCifProcessing.cs index 8ca744d..7446666 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Crystals/InvalidCifProcessing.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Crystals/InvalidCifProcessing.cs @@ -55,7 +55,7 @@ public async Task CrystalProcessing_InvalidCif_GenerateExpectedFileAggregate() var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.ShouldBeEquivalentTo(new + file.Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Records, diff --git a/Sds.Osdr.IntegrationTests/Tests/Crystals/InvalidCifProcessingImageGeneration.cs b/Sds.Osdr.IntegrationTests/Tests/Crystals/InvalidCifProcessingImageGeneration.cs index 35f0e8d..8c910c3 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Crystals/InvalidCifProcessingImageGeneration.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Crystals/InvalidCifProcessingImageGeneration.cs @@ -56,7 +56,7 @@ public async Task CrystalProcessing_InvalidCif_GenerateExpectedFileAggregate() var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.ShouldBeEquivalentTo(new + file.Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Records, diff --git a/Sds.Osdr.IntegrationTests/Tests/Crystals/ValidCifProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/Crystals/ValidCifProcessing.cs index 71ac980..9b38fa1 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Crystals/ValidCifProcessing.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Crystals/ValidCifProcessing.cs @@ -61,7 +61,7 @@ public async Task CrystalProcessing_ValidCif_GenerateExceptedFileAggregate() var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.ShouldBeEquivalentTo(new + file.Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Records, @@ -126,7 +126,7 @@ public async Task CrystalProcessing_ValidCif_GenerateExceptedRecordAggregate() var record = await Session.Get(recordId); record.Should().NotBeNull(); - record.ShouldBeEquivalentTo(new + record.Should().BeEquivalentTo(new { Id = recordId, RecordType = RecordType.Crystal, diff --git a/Sds.Osdr.IntegrationTests/Tests/Folders/CreateNewFolder.cs b/Sds.Osdr.IntegrationTests/Tests/Folders/CreateNewFolder.cs index 2d5fb9d..3a8d0f1 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Folders/CreateNewFolder.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Folders/CreateNewFolder.cs @@ -39,7 +39,7 @@ public async Task CreateFlder_NewFolder_RegisterNewFolder() var folder = await Session.Get(FolderId); folder.Should().NotBeNull(); - folder.Should().ShouldBeEquivalentTo(new { + folder.Should().BeEquivalentTo(new { Id = FolderId, OwnedBy = JohnId, CreatedBy= JohnId, diff --git a/Sds.Osdr.IntegrationTests/Tests/GenegicFiles/ArchiveGzProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/GenegicFiles/ArchiveGzProcessing.cs index e96cf34..0fd3d0d 100644 --- a/Sds.Osdr.IntegrationTests/Tests/GenegicFiles/ArchiveGzProcessing.cs +++ b/Sds.Osdr.IntegrationTests/Tests/GenegicFiles/ArchiveGzProcessing.cs @@ -48,7 +48,7 @@ public async Task GenericProcessing_ValidGz_GeneratesAppropriateModels() var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.ShouldBeEquivalentTo(new + file.Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Generic, diff --git a/Sds.Osdr.IntegrationTests/Tests/GenegicFiles/ImageJpgProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/GenegicFiles/ImageJpgProcessing.cs index 53bb056..c45350f 100644 --- a/Sds.Osdr.IntegrationTests/Tests/GenegicFiles/ImageJpgProcessing.cs +++ b/Sds.Osdr.IntegrationTests/Tests/GenegicFiles/ImageJpgProcessing.cs @@ -46,7 +46,7 @@ public async Task ImageProcessing_ValidJpg_GeneratesAppropriateModels() var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.ShouldBeEquivalentTo(new + file.Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Image, diff --git a/Sds.Osdr.IntegrationTests/Tests/MachineLearning/PredictPropertiesValidCase.cs b/Sds.Osdr.IntegrationTests/Tests/MachineLearning/PredictPropertiesValidCase.cs index 8a5da6b..8f8a787 100644 --- a/Sds.Osdr.IntegrationTests/Tests/MachineLearning/PredictPropertiesValidCase.cs +++ b/Sds.Osdr.IntegrationTests/Tests/MachineLearning/PredictPropertiesValidCase.cs @@ -47,7 +47,7 @@ public async Task MlPrediction_SingleCsvFileProcessed() fileId.Should().NotBe(Guid.Empty); var file = await Session.Get(fileId); - file.Should().ShouldBeEquivalentTo(new + file.Should().Should().BeEquivalentTo(new { Id = fileId, ParentId = FolderId, diff --git a/Sds.Osdr.IntegrationTests/Tests/MachineLearning/TrainOneValidModel.cs b/Sds.Osdr.IntegrationTests/Tests/MachineLearning/TrainOneValidModel.cs index 0c41d3c..67d1ee3 100644 --- a/Sds.Osdr.IntegrationTests/Tests/MachineLearning/TrainOneValidModel.cs +++ b/Sds.Osdr.IntegrationTests/Tests/MachineLearning/TrainOneValidModel.cs @@ -192,7 +192,7 @@ public async Task MlProcessing_ModelTraining_ModelDependentFilesAndReportBelongT var file = await Harness.Session.Get(fileId); var fileEntity = Files.Find(new BsonDocument("_id", fileId)).FirstOrDefault() as IDictionary; - fileEntity["OwnedBy"].ShouldBeEquivalentTo(JohnId); + fileEntity["OwnedBy"].Should().BeEquivalentTo(JohnId); } var reportFiles = Harness.GetDependentFiles(FolderId, FileType.Image, FileType.Tabular, FileType.Pdf); @@ -201,7 +201,7 @@ public async Task MlProcessing_ModelTraining_ModelDependentFilesAndReportBelongT var file = await Harness.Session.Get(fileId); var fileEntity = Files.Find(new BsonDocument("_id", fileId)).FirstOrDefault() as IDictionary; - fileEntity["OwnedBy"].ShouldBeEquivalentTo(JohnId); + fileEntity["OwnedBy"].Should().BeEquivalentTo(JohnId); } } } diff --git a/Sds.Osdr.IntegrationTests/Tests/Microscopy/ValidMicroscopyProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/Microscopy/ValidMicroscopyProcessing.cs new file mode 100644 index 0000000..064e398 --- /dev/null +++ b/Sds.Osdr.IntegrationTests/Tests/Microscopy/ValidMicroscopyProcessing.cs @@ -0,0 +1,99 @@ +using FluentAssertions; +using Leanda.Microscopy.Domain; +using MongoDB.Bson; +using MongoDB.Driver; +using Sds.Osdr.Generic.Domain; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.IntegrationTests +{ + public class ValidMicroscopyProcessingFixture + { + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + + public ValidMicroscopyProcessingFixture(OsdrTestHarness harness) + { + BlobId = harness.JohnBlobStorageClient.AddResource(harness.JohnId.ToString(), "Nikon_BF007.nd2", new Dictionary() { { "parentId", harness.JohnId } }).Result; + + FileId = harness.WaitWhileFileProcessed(BlobId); + } + } + + [Collection("OSDR Test Harness")] + public class ValidMicroscopyProcessing : OsdrTest, IClassFixture + { + private Guid BlobId { get; set; } + private Guid FileId { get; set; } + + public ValidMicroscopyProcessing(OsdrTestHarness fixture, ITestOutputHelper output, ValidMicroscopyProcessingFixture initFixture) : base(fixture, output) + { + BlobId = initFixture.BlobId; + FileId = initFixture.FileId; + } + + [Fact, ProcessingTrait(TraitGroup.All, TraitGroup.Microscopy)] + public async Task MicroscopyProcessing_ValidNd2_There_Are_No_Errors() + { + Harness.GetFaults().Should().BeEmpty(); + + await Task.CompletedTask; + } + + [Fact, ProcessingTrait(TraitGroup.All, TraitGroup.Microscopy)] + public async Task MicroscopyProcessing_ValidNd2_GeneratesAppropriateModels() + { + var blobInfo = await BlobStorage.GetFileInfo(BlobId, JohnId.ToString()); + blobInfo.Should().NotBeNull(); + + var file = await Session.Get(FileId); + file.Should().NotBeNull(); + file.Should().BeEquivalentTo(new + { + Id = FileId, + Type = FileType.Microscopy, + Bucket = JohnId.ToString(), + BlobId = BlobId, + PdfBucket = file.Bucket, + OwnedBy = JohnId, + CreatedBy = JohnId, + CreatedDateTime = DateTimeOffset.UtcNow, + UpdatedBy = JohnId, + UpdatedDateTime = DateTimeOffset.UtcNow, + ParentId = JohnId, + FileName = blobInfo.FileName, + Length = blobInfo.Length, + Md5 = blobInfo.MD5, + IsDeleted = false, + Status = FileStatus.Processed + }, options => options + .ExcludingMissingMembers() + ); + file.Images.Should().NotBeNullOrEmpty(); + file.Images.Should().HaveCount(3); + } + [Fact, ProcessingTrait(TraitGroup.All, TraitGroup.Microscopy)] + public async Task MicroscopyProcessing_ValidNd2_ExpectedFileEntity() + { + var file = await Session.Get(FileId); + var fileView = Files.Find(new BsonDocument("_id", FileId)).FirstOrDefault() as IDictionary; + + fileView.Should().EntityShouldBeEquivalentTo(file); + } + [Fact, ProcessingTrait(TraitGroup.All, TraitGroup.Microscopy)] + public async Task MicroscopyProcessing_ValidNd2_ExpectedFileNode() + { + var file = await Session.Get(FileId); + var fileNode = Nodes.Find(new BsonDocument("_id", FileId)).FirstOrDefault() as IDictionary; + + fileNode.Should().NodeShouldBeEquivalentTo(file); + } + } +} diff --git a/Sds.Osdr.IntegrationTests/Tests/OfficeFiles/MsWordProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/OfficeFiles/MsWordProcessing.cs index 0494b71..7592648 100644 --- a/Sds.Osdr.IntegrationTests/Tests/OfficeFiles/MsWordProcessing.cs +++ b/Sds.Osdr.IntegrationTests/Tests/OfficeFiles/MsWordProcessing.cs @@ -55,7 +55,7 @@ public async Task OfficeProcessing_ValidMsWord_GeneratesAppropriateModels() var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.Should().ShouldBeEquivalentTo(new { + file.Should().Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Office, Bucket = JohnId.ToString(), diff --git a/Sds.Osdr.IntegrationTests/Tests/Pdf/PdfProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/Pdf/PdfProcessing.cs index 363a0e9..89f3a30 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Pdf/PdfProcessing.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Pdf/PdfProcessing.cs @@ -46,7 +46,7 @@ public async Task PdfProcessing_ValidPdf_GeneratesAppropriateModels() var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.Should().ShouldBeEquivalentTo(new + file.Should().Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Pdf, diff --git a/Sds.Osdr.IntegrationTests/Tests/Reactions/InvalidRxnProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/Reactions/InvalidRxnProcessing.cs index 080e287..7949b6b 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Reactions/InvalidRxnProcessing.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Reactions/InvalidRxnProcessing.cs @@ -55,7 +55,7 @@ public async Task ReactionProcessing_InvalidRxn_GeneratesExpectedFileAggregate() var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.ShouldBeEquivalentTo(new + file.Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Records, diff --git a/Sds.Osdr.IntegrationTests/Tests/Reactions/ValidRxnProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/Reactions/ValidRxnProcessing.cs index 1812b66..55eef6b 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Reactions/ValidRxnProcessing.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Reactions/ValidRxnProcessing.cs @@ -58,7 +58,7 @@ public async Task ReactionProcessing_ValidRxn_GenerateExpectedFileAggregate() var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.ShouldBeEquivalentTo(new + file.Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Records, @@ -126,7 +126,7 @@ public async Task ReactionProcessing_ValidRxn_GenerateExpectedRactionAggregate() var record = await Session.Get(recordId); record.Should().NotBeNull(); - record.ShouldBeEquivalentTo(new + record.Should().BeEquivalentTo(new { Id = recordId, RecordType = RecordType.Reaction, diff --git a/Sds.Osdr.IntegrationTests/Tests/Spectra/InvalidJdxProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/Spectra/InvalidJdxProcessing.cs index 37b8c2c..520b9c8 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Spectra/InvalidJdxProcessing.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Spectra/InvalidJdxProcessing.cs @@ -55,7 +55,7 @@ public async Task SpectrumProcessing_InvalidJdx_GeneratesExpectedFileAggregate() var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.ShouldBeEquivalentTo(new + file.Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Records, diff --git a/Sds.Osdr.IntegrationTests/Tests/Spectra/ValidJdxProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/Spectra/ValidJdxProcessing.cs index 473f9a8..0cc2953 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Spectra/ValidJdxProcessing.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Spectra/ValidJdxProcessing.cs @@ -58,7 +58,7 @@ public async Task SpectrumProcessing_ValidJdx_GeneratesExpectedFileAggregate() var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.Should().ShouldBeEquivalentTo(new + file.Should().Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Records, @@ -138,7 +138,7 @@ public async Task SpectrumProcessing_ValidJdx_GeneratesExpectedRecordAggregate() var recordId = Harness.GetProcessedRecords(FileId).First(); var record = await Session.Get(recordId); record.Should().NotBeNull(); - record.Should().ShouldBeEquivalentTo(new + record.Should().Should().BeEquivalentTo(new { Id = recordId, RecordType = RecordType.Spectrum, diff --git a/Sds.Osdr.IntegrationTests/Tests/Streams/ReadEventsBackwardAsyncFromMolFile.cs b/Sds.Osdr.IntegrationTests/Tests/Streams/ReadEventsBackwardAsyncFromMolFile.cs index 37e6b3e..31c1d51 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Streams/ReadEventsBackwardAsyncFromMolFile.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Streams/ReadEventsBackwardAsyncFromMolFile.cs @@ -43,8 +43,8 @@ public async Task Streams_GetStream_ExpectedValidOneStream() var events = await EventStore.ReadEventsBackwardAsync(file.Id, 0, 1); var oneEvent = events.First(); - oneEvent.Id.ShouldBeEquivalentTo(FileId); - oneEvent.Version.ShouldBeEquivalentTo(1); + oneEvent.Id.Should().Be(FileId); + oneEvent.Version.Should().Be(1); } [Fact, ProcessingTrait(TraitGroup.All, TraitGroup.Chemical)] diff --git a/Sds.Osdr.IntegrationTests/Tests/Substances/InvalidMolProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/Substances/InvalidMolProcessing.cs index 6802dda..19fdcb4 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Substances/InvalidMolProcessing.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Substances/InvalidMolProcessing.cs @@ -58,7 +58,7 @@ public async Task ChemicalProcessing_InvalidMol_GenerateExpectedFileAggregate() var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.ShouldBeEquivalentTo(new + file.Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Records, @@ -119,7 +119,7 @@ public async Task ChemicalProcessing_InvalidMol_GenerateExpectedInvalidRecord() var invalidRecord = await Session.Get(recordId); invalidRecord.Should().NotBeNull(); - invalidRecord.ShouldBeEquivalentTo(new + invalidRecord.Should().BeEquivalentTo(new { Id = recordId, Error = "molfile loader: ring bond count is allowed only for queries", diff --git a/Sds.Osdr.IntegrationTests/Tests/Substances/InvalidSdfProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/Substances/InvalidSdfProcessing.cs index 97ac999..350e1fd 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Substances/InvalidSdfProcessing.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Substances/InvalidSdfProcessing.cs @@ -57,7 +57,7 @@ public async Task ChemicalProcessing_InvalidSdf_GenerateExceptedFileAggregate() var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.ShouldBeEquivalentTo(new + file.Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Records, @@ -104,7 +104,7 @@ public async Task ChemicalProcessing_InvalidSdfWithTwentyRecords_GenerateExpecte var invalidRecord = await Session.Get(recordId); invalidRecord.Should().NotBeNull(); - invalidRecord.ShouldBeEquivalentTo(new + invalidRecord.Should().BeEquivalentTo(new { Id = recordId, Error = "sdffile loader: could not process file", diff --git a/Sds.Osdr.IntegrationTests/Tests/Substances/SdfProcessingWithOneValidAndOneInvalidRecords.cs b/Sds.Osdr.IntegrationTests/Tests/Substances/SdfProcessingWithOneValidAndOneInvalidRecords.cs index 1fada44..f5283e3 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Substances/SdfProcessingWithOneValidAndOneInvalidRecords.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Substances/SdfProcessingWithOneValidAndOneInvalidRecords.cs @@ -57,7 +57,7 @@ public async Task ChemicalProcessing_OneValidSdfAndOneInvalid_GenerateExpectedFi var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.ShouldBeEquivalentTo(new + file.Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Records, @@ -116,7 +116,7 @@ public async Task ChemicalProcessing_OneValidSdfAndOneInvalid_GenerateExpectedIn var invalidRecord = await Session.Get(recordId); invalidRecord.Should().NotBeNull(); - invalidRecord.ShouldBeEquivalentTo(new + invalidRecord.Should().BeEquivalentTo(new { Id = recordId, Error = "sdffile loader: could not process file", @@ -162,7 +162,7 @@ public async Task ChemicalProcessing_OneValidSdfAndOneInvalid_GenerateExpectedVa var validRecord = await Session.Get(recordId); validRecord.Should().NotBeNull(); - validRecord.ShouldBeEquivalentTo(new + validRecord.Should().BeEquivalentTo(new { Id = recordId, RecordType = RecordType.Structure, diff --git a/Sds.Osdr.IntegrationTests/Tests/Substances/ValidCdxProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/Substances/ValidCdxProcessing.cs index a2a33c9..fcc901f 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Substances/ValidCdxProcessing.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Substances/ValidCdxProcessing.cs @@ -57,7 +57,7 @@ public async Task ChemicalProcessing_ValidCdx_GenerateExpectedRecordsFileAggrega var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.ShouldBeEquivalentTo(new + file.Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Records, @@ -124,7 +124,7 @@ public async Task ChemicalProcessing_ValidCdxWithThreeRecords_GenerateExpectedRe var record = await Session.Get((Guid)recordId); record.Should().NotBeNull(); - record.ShouldBeEquivalentTo(new + record.Should().BeEquivalentTo(new { Id = recordId, RecordType = RecordType.Structure, diff --git a/Sds.Osdr.IntegrationTests/Tests/Substances/ValidMolProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/Substances/ValidMolProcessing.cs index 0c35232..65af8cc 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Substances/ValidMolProcessing.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Substances/ValidMolProcessing.cs @@ -57,7 +57,7 @@ public async Task ChemicalProcessing_ValidMol_GenerateExpectedRecordsFileAggrega var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.ShouldBeEquivalentTo(new + file.Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Records, @@ -116,7 +116,7 @@ public async Task ChemicalProcessing_ValidMol_GenerateExpectedSubstanceAggregate var record = await Session.Get(recordId); record.Should().NotBeNull(); - record.ShouldBeEquivalentTo(new + record.Should().BeEquivalentTo(new { Id = recordId, RecordType = RecordType.Structure, diff --git a/Sds.Osdr.IntegrationTests/Tests/Substances/ValidSdfProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/Substances/ValidSdfProcessing.cs index 8c9ac37..ea2ddda 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Substances/ValidSdfProcessing.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Substances/ValidSdfProcessing.cs @@ -58,7 +58,7 @@ public async Task ChemicalProcessing_ValidSdf_GenerateExpectedFileAggregate() var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.ShouldBeEquivalentTo(new + file.Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Records, @@ -133,7 +133,7 @@ public async Task ChemicalProcessing_ValidSdf_GenerateSubstanceAggregate() var record = await Session.Get(recordId); record.Should().NotBeNull(); - record.ShouldBeEquivalentTo(new + record.Should().BeEquivalentTo(new { Id = recordId, RecordType = RecordType.Structure, diff --git a/Sds.Osdr.IntegrationTests/Tests/Tabular/CsvProcessing.cs b/Sds.Osdr.IntegrationTests/Tests/Tabular/CsvProcessing.cs index 1ed40da..4ea3f89 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Tabular/CsvProcessing.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Tabular/CsvProcessing.cs @@ -54,7 +54,7 @@ public async Task TabularProcessing_ValidCsv_GeneratesExpectedFileAggregate() var file = await Session.Get(FileId); file.Should().NotBeNull(); - file.Should().ShouldBeEquivalentTo(new + file.Should().Should().BeEquivalentTo(new { Id = FileId, Type = FileType.Tabular, diff --git a/Sds.Osdr.IntegrationTests/Tests/Users/CreateNewUser.cs b/Sds.Osdr.IntegrationTests/Tests/Users/CreateNewUser.cs index b048f94..b678cd4 100644 --- a/Sds.Osdr.IntegrationTests/Tests/Users/CreateNewUser.cs +++ b/Sds.Osdr.IntegrationTests/Tests/Users/CreateNewUser.cs @@ -41,7 +41,7 @@ public async Task CreateUser_JohnDoe_RegisterNewUser() var user = await Session.Get(NewUserId); user.Should().NotBeNull(); - user.ShouldBeEquivalentTo(new + user.Should().BeEquivalentTo(new { Id = NewUserId, CreatedBy = JohnId, diff --git a/Sds.Osdr.IntegrationTests/Traits/TraitGroup.cs b/Sds.Osdr.IntegrationTests/Traits/TraitGroup.cs index e21f760..3336ab4 100644 --- a/Sds.Osdr.IntegrationTests/Traits/TraitGroup.cs +++ b/Sds.Osdr.IntegrationTests/Traits/TraitGroup.cs @@ -21,6 +21,8 @@ public enum TraitGroup Sharing, DummyAuthentication, NotAuthorized, - Stream + Stream, + Microscopy, + Categories } } diff --git a/Sds.Osdr.IntegrationTests/appsettings.json b/Sds.Osdr.IntegrationTests/appsettings.json index 1d0bb93..fe24980 100644 --- a/Sds.Osdr.IntegrationTests/appsettings.json +++ b/Sds.Osdr.IntegrationTests/appsettings.json @@ -1,7 +1,13 @@ { + "KeyCloak": { + "Authority": "%IDENTITY_SERVER_URL%" + }, "MongoDb": { "ConnectionString": "%OSDR_MONGO_DB%" }, + "ElasticSearch": { + "ConnectionString": "%OSDR_ES%" + }, "GridFs": { "ConnectionString": "%OSDR_GRID_FS%" }, @@ -33,6 +39,9 @@ "pathFormat": "%OSDR_LOG_FOLDER%/sds-osdr-integrationtests-{Date}.log", "retainedFileCountLimit": 5 } + }, + { + "Name": "Console" } ] } diff --git a/Sds.Osdr.IntegrationTests/docker-compose.yml b/Sds.Osdr.IntegrationTests/docker-compose.yml index c3684b4..d0dd5d0 100644 --- a/Sds.Osdr.IntegrationTests/docker-compose.yml +++ b/Sds.Osdr.IntegrationTests/docker-compose.yml @@ -7,35 +7,35 @@ services: - "2113:2113" - "1113:1113" environment: - - RUN_PROJECTIONS = All + - RUN_PROJECTIONS=All networks: - - osdr-test + - leanda-net redis: image: redis:4-alpine command: redis-server --appendonly yes - # ports: - # - "6379:6379" + ports: + - "6379:6379" networks: - - osdr-test + - leanda-net rabbitmq: - image: docker.your-company.com/osdr-rabbitmq:3.6 - hostname: "rabbitmq-test" + image: leanda/rabbitmq + hostname: "leanda" environment: - - RABBITMQ_DEFAULT_VHOST=osdr_test - # ports: - # - "18282:15672" - # - "5672:5672" + - RABBITMQ_DEFAULT_VHOST=leanda + ports: + - "8282:15672" + - "5672:5672" networks: - - osdr-test + - leanda-net mongo: image: mongo:3.6 - # ports: - # - "27017:27017" + ports: + - "27017:27017" networks: - - osdr-test + - leanda-net postgres: image: postgres @@ -46,7 +46,7 @@ services: POSTGRES_ROOT_PASSWORD: keycloak pgdata: data-pstgresql networks: - - osdr-test + - leanda-net keycloak: build: KeyCloak @@ -59,21 +59,32 @@ services: POSTGRES_PORT_5432_TCP_ADDR: postgres POSTGRES_DATABASE: keycloak JDBC_PARAMS: 'connectTimeout=30' - # ports: - # - '8080:8080' + ports: + - '8080:8080' networks: - - osdr-test + - leanda-net depends_on: - postgres - osdr-service-backend: - container_name: osdr-service-backend - image: docker.your-company.com/osdr-service-backend:ci-${BUILD_NUMBER} + elasticsearch: + container_name: elasticsearch + image: leanda/elasticsearch + environment: + - discovery.type=single-node + ports: + - "9201:9201" + - "9200:9200" + - "9301:9300" + networks: + - leanda-net + + core-backend: + container_name: leanda-backend + image: leanda/core-backend:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - OSDR_EVENT_STORE=ConnectTo=tcp://admin:changeit@eventstore:1113 - OSDR_REDIS=redis - OSDR_LOG_LEVEL=${OSDR_LOG_LEVEL} @@ -81,21 +92,20 @@ services: volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test + - leanda-net depends_on: - rabbitmq - eventstore - redis - mongo - osdr-service-frontend: - container_name: osdr-service-frontend - image: docker.your-company.com/osdr-service-frontend:ci-${BUILD_NUMBER} + core-frontend: + container_name: leanda-frontend + image: leanda/core-frontend:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - OSDR_EVENT_STORE=ConnectTo=tcp://admin:changeit@eventstore:1113 - OSDR_REDIS=redis - OSDR_LOG_LEVEL=${OSDR_LOG_LEVEL} @@ -103,94 +113,97 @@ services: volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test + - leanda-net depends_on: - rabbitmq - eventstore - redis - mongo - osdr-service-sagahost: - container_name: osdr-service-sagahost - image: docker.your-company.com/osdr-service-sagahost:ci-${BUILD_NUMBER} + core-sagahost: + container_name: leanda-sagahost + image: leanda/core-sagahost:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - OSDR_LOG_LEVEL=${OSDR_LOG_LEVEL} command: ./wait-for-it.sh rabbitmq:15672 -t 30 -- ./Sds.Osdr.Domain.SagaHost volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test + - leanda-net depends_on: - rabbitmq - mongo - osdr-service-persistence: - container_name: osdr-service-persistence - image: docker.your-company.com/osdr-service-persistence:ci-${BUILD_NUMBER} + core-persistence: + container_name: leanda-persistence + image: leanda/core-persistence:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - OSDR_LOG_LEVEL=${OSDR_LOG_LEVEL} command: ./wait-for-it.sh rabbitmq:15672 -t 30 -- ./Sds.Osdr.Persistence volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test + - leanda-net depends_on: - rabbitmq - mongo blob-storage-api: - container_name: osdr-blob-storage-api - image: docker.your-company.com/blob-storage-webapi:latest + container_name: blob-storage-api + image: leanda/blob-storage-webapi entrypoint: /bin/bash environment: - IDENTITY_SERVER_URL=http://keycloak:8080/auth/realms/OSDR - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - MAX_BLOB_SIZE=419430400 + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - SWAGGER_BASEPATH=/blob/v1 command: ./wait-for-it.sh rabbitmq:15672 -t 30 -- ./wait-for-it.sh keycloak:8080 -t 30 -- ./Sds.Storage.Blob.WebApi volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test - # ports: - # - "18006:18006" + - leanda-net + ports: + - "18006:18006" depends_on: + - rabbitmq + - mongo - keycloak integration: - container_name: osdr-integration-tests - image: docker.your-company.com/osdr-service-integration:ci-${BUILD_NUMBER} + container_name: leandas-integration-tests + image: leanda/integration:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - IDENTITY_SERVER_URL=http://keycloak:8080/auth/realms/OSDR - # - OSDR_REDIS=redis - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev - - OSDR_GRID_FS=mongodb://mongo:27017/osdr_dev - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_ES=http://elasticsearch:9200 + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_GRID_FS=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - OSDR_EVENT_STORE=ConnectTo=tcp://admin:changeit@eventstore:1113 - #command: ./wait-for-it.sh rabbitmq:15672 -t 30 -- ./wait-for-it.sh http://blob-storage-api:18006 -t 30 -- dotnet vstest ./Sds.Osdr.IntegrationTests.dll /logger:console;verbosity="normal" - command: ./wait-for-it.sh rabbitmq:15672 -t 30 -- ./wait-for-it.sh http://blob-storage-api:18006 -t 30 -- dotnet vstest ./Sds.Osdr.IntegrationTests.dll /logger:"trx;LogFileName=integrationtests-results-${BUILD_NUMBER}.xml" /ResultsDirectory:/results + command: ./wait-for-it.sh rabbitmq:15672 -t 30 -- ./wait-for-it.sh http://blob-storage-api:18006 -t 30 -- dotnet vstest ./Sds.Osdr.IntegrationTests.dll /logger:console;verbosity="normal" + #command: ./wait-for-it.sh rabbitmq:15672 -t 30 -- ./wait-for-it.sh http://blob-storage-api:18006 -t 30 -- dotnet vstest ./Sds.Osdr.IntegrationTests.dll /logger:"trx;LogFileName=integrationtests-results-${BUILD_NUMBER}.xml" /ResultsDirectory:/results volumes: - ${OSDR_LOG_FOLDER}:/logs - ${OSDR_LOG_FOLDER}:/results networks: - - osdr-test + - leanda-net depends_on: - blob-storage-api - - osdr-service-backend - - osdr-service-frontend - - osdr-service-sagahost - - osdr-service-persistence + - core-backend + - core-frontend + - core-sagahost + - core-persistence networks: - osdr-test: + leanda-net: diff --git a/Sds.Osdr.MachineLearning/Sds.Osdr.MachineLearning.csproj b/Sds.Osdr.MachineLearning/Sds.Osdr.MachineLearning.csproj index 78d25ee..01c0096 100644 --- a/Sds.Osdr.MachineLearning/Sds.Osdr.MachineLearning.csproj +++ b/Sds.Osdr.MachineLearning/Sds.Osdr.MachineLearning.csproj @@ -1,8 +1,13 @@  - 0.2.0 netstandard2.0 Debug;Release;Dev + true + Science Data Software + Science Data SoftwareScience Data Software + Open Science Data Repository + https://opensource.org/licenses/MIT + 0.2.0 diff --git a/Sds.Osdr.Persistence/Dockerfile b/Sds.Osdr.Persistence/Dockerfile index 2c208cf..9f68a4e 100644 --- a/Sds.Osdr.Persistence/Dockerfile +++ b/Sds.Osdr.Persistence/Dockerfile @@ -5,6 +5,8 @@ ARG RID=linux-x64 WORKDIR /build +COPY Leanda.Microscopy/Leanda.Microscopy.csproj Leanda.Microscopy/ +COPY Leanda.Categories/Leanda.CategoryTree.csproj Leanda.Categories/ COPY Sds.Osdr.Chemicals/Sds.Osdr.Chemicals.csproj Sds.Osdr.Chemicals/ COPY Sds.Osdr.Crystals/Sds.Osdr.Crystals.csproj Sds.Osdr.Crystals/ COPY Sds.Osdr.Domain/Sds.Osdr.Domain.csproj Sds.Osdr.Domain/ @@ -24,6 +26,8 @@ COPY Nuget.config . RUN dotnet restore --configfile Nuget.config Sds.Osdr.Persistence/Sds.Osdr.Persistence.csproj +COPY Leanda.Microscopy Leanda.Microscopy +COPY Leanda.Categories Leanda.Categories COPY Sds.Osdr.Chemicals Sds.Osdr.Chemicals COPY Sds.Osdr.Crystals Sds.Osdr.Crystals COPY Sds.Osdr.Domain Sds.Osdr.Domain diff --git a/Sds.Osdr.Persistence/Nuget.config b/Sds.Osdr.Persistence/Nuget.config index bfc7af6..e99c073 100644 --- a/Sds.Osdr.Persistence/Nuget.config +++ b/Sds.Osdr.Persistence/Nuget.config @@ -4,7 +4,6 @@ - \ No newline at end of file diff --git a/Sds.Osdr.Persistence/PersistenceService.cs b/Sds.Osdr.Persistence/PersistenceService.cs index 27a2cd4..90d67b3 100644 --- a/Sds.Osdr.Persistence/PersistenceService.cs +++ b/Sds.Osdr.Persistence/PersistenceService.cs @@ -60,8 +60,11 @@ public void Start() services.AddOptions(); services.Configure(Configuration.GetSection("MassTransit")); - services.AddSingleton(new MongoClient(Environment.ExpandEnvironmentVariables(Configuration["ConnectionSettings:ConnectionString"]))); - services.AddScoped(service => service.GetService().GetDatabase(Configuration["ConnectionSettings:DatabaseName"])); + var mongoConnectionString = Environment.ExpandEnvironmentVariables(Configuration["ConnectionSettings:ConnectionString"]); + var mongoUrl = new MongoUrl(mongoConnectionString); + + services.AddSingleton(new MongoClient(mongoUrl)); + services.AddScoped(service => service.GetService().GetDatabase(mongoUrl.DatabaseName)); services.AddAllConsumers(); @@ -79,6 +82,8 @@ public void Start() Assembly.LoadFrom("Sds.Osdr.Tabular.dll"), Assembly.LoadFrom("Sds.Osdr.MachineLearning.dll"), Assembly.LoadFrom("Sds.Osdr.WebPage.dll"), + Assembly.LoadFrom("Leanda.Microscopy.dll"), + Assembly.LoadFrom("Leanda.CategoryTree.dll"), }; Log.Information($"Registered modules:"); @@ -162,8 +167,7 @@ public void Start() foreach (var field in collectionIndexes) { - mongoCollection.Indexes.CreateOneAsync(Builders.IndexKeys - .Ascending(_ => _[field])).GetAwaiter().GetResult(); + mongoCollection.Indexes.CreateOneAsync(Builders.IndexKeys.Ascending(_ => _[field])).GetAwaiter().GetResult(); } Console.Write(""); diff --git a/Sds.Osdr.Persistence/Sds.Osdr.Persistence.csproj b/Sds.Osdr.Persistence/Sds.Osdr.Persistence.csproj index 3136296..eb03d1c 100644 --- a/Sds.Osdr.Persistence/Sds.Osdr.Persistence.csproj +++ b/Sds.Osdr.Persistence/Sds.Osdr.Persistence.csproj @@ -25,11 +25,11 @@ - + - - + + @@ -41,6 +41,8 @@ + + diff --git a/Sds.Osdr.Persistence/appsettings.json b/Sds.Osdr.Persistence/appsettings.json index e72eff0..66c53e4 100644 --- a/Sds.Osdr.Persistence/appsettings.json +++ b/Sds.Osdr.Persistence/appsettings.json @@ -1,17 +1,16 @@ { "ConnectionSettings": { "ConnectionString": "%OSDR_MONGO_DB%", - "DatabaseName": "osdr_dev", - "Indexes": [ - { - "Name": "Nodes", - "Fields": [ "ParentId", "IsDeleted", "Name" ] - }, - { - "Name": "AccessPermissions", - "Fields": [ "IsPublic" ] - } - ] + "Indexes": [ + { + "Name": "Nodes", + "Fields": [ "ParentId", "IsDeleted", "Name" ] + }, + { + "Name": "AccessPermissions", + "Fields": [ "IsPublic" ] + } + ] }, "HeartBeat": { "TcpPort": 11000 @@ -26,19 +25,19 @@ "RedeliveryInterval": 100 }, "Serilog": { - "MinimumLevel": "Error", - "WriteTo": [ - { - "Name": "RollingFile", - "Args": { - "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] [v{SourceSystemInformationalVersion}] {Message}{NewLine}{Exception}", - "pathFormat": "%OSDR_LOG_FOLDER%/sds-osdr-persistance-service-{Date}.log", - "retainedFileCountLimit": 5 - } - }, - { - "Name": "Console" + "MinimumLevel": "Error", + "WriteTo": [ + { + "Name": "RollingFile", + "Args": { + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] [v{SourceSystemInformationalVersion}] {Message}{NewLine}{Exception}", + "pathFormat": "%OSDR_LOG_FOLDER%/sds-osdr-persistance-service-{Date}.log", + "retainedFileCountLimit": 5 } - ] - } + }, + { + "Name": "Console" + } + ] } +} diff --git a/Sds.Osdr.RecordsFile/Persistence/CommandHandlers/AggregatePropertiesCommandHandler.cs b/Sds.Osdr.RecordsFile/Persistence/CommandHandlers/AggregatePropertiesCommandHandler.cs index 0c91077..73a6d9a 100644 --- a/Sds.Osdr.RecordsFile/Persistence/CommandHandlers/AggregatePropertiesCommandHandler.cs +++ b/Sds.Osdr.RecordsFile/Persistence/CommandHandlers/AggregatePropertiesCommandHandler.cs @@ -28,8 +28,7 @@ public async Task Consume(ConsumeContext context) .Unwind(d => d["Properties.ChemicalProperties"]) .Group(new BsonDocument { { "_id", "$Properties.ChemicalProperties.Name" } }) .ToList() - .Select(d=> d.GetValue(0).ToString()) - ; + .Select(d=> d.GetValue(0).ToString()); await context.Publish(new { diff --git a/Sds.Osdr.RecordsFile/Sds.Osdr.RecordsFile.csproj b/Sds.Osdr.RecordsFile/Sds.Osdr.RecordsFile.csproj index e235438..087d7ab 100644 --- a/Sds.Osdr.RecordsFile/Sds.Osdr.RecordsFile.csproj +++ b/Sds.Osdr.RecordsFile/Sds.Osdr.RecordsFile.csproj @@ -7,12 +7,16 @@ Records file processing domain module false First release - Copyright 2017 (c) Science Data Software. All rights reserved. + domain, record, module Debug;Release;Dev + true + https://opensource.org/licenses/MIT + 0.13.0 + Open Science Data Repository - + diff --git a/Sds.Osdr.WebApi.IntegrationTests/.env.jenkins b/Sds.Osdr.WebApi.IntegrationTests/.env.jenkins deleted file mode 100644 index 3d8dd6c..0000000 --- a/Sds.Osdr.WebApi.IntegrationTests/.env.jenkins +++ /dev/null @@ -1,2 +0,0 @@ -OSDR_LOG_FOLDER=/logs -OSDR_LOG_LEVEL=Error \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/.env.travis-ci b/Sds.Osdr.WebApi.IntegrationTests/.env.travis-ci new file mode 100644 index 0000000..6167394 --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/.env.travis-ci @@ -0,0 +1,3 @@ +OSDR_LOG_FOLDER=/logs +OSDR_LOG_LEVEL=Error +TAG_VERSION=ci \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Dockerfile b/Sds.Osdr.WebApi.IntegrationTests/Dockerfile index 1cfdda0..d963faf 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Dockerfile +++ b/Sds.Osdr.WebApi.IntegrationTests/Dockerfile @@ -4,6 +4,8 @@ ARG RID=linux-x64 WORKDIR /build +COPY Leanda.Microscopy/Leanda.Microscopy.csproj Leanda.Microscopy/ +COPY Leanda.Categories/Leanda.CategoryTree.csproj Leanda.Categories/ COPY Sds.Osdr.Chemicals/Sds.Osdr.Chemicals.csproj Sds.Osdr.Chemicals/ COPY Sds.Osdr.Crystals/Sds.Osdr.Crystals.csproj Sds.Osdr.Crystals/ COPY Sds.Osdr.Domain/Sds.Osdr.Domain.csproj Sds.Osdr.Domain/ @@ -24,6 +26,8 @@ COPY Nuget.config . RUN dotnet restore --configfile Nuget.config Sds.Osdr.WebApi.IntegrationTests/Sds.Osdr.WebApi.IntegrationTests.csproj +COPY Leanda.Microscopy Leanda.Microscopy +COPY Leanda.Categories Leanda.Categories COPY Sds.Osdr.Chemicals Sds.Osdr.Chemicals COPY Sds.Osdr.Crystals Sds.Osdr.Crystals COPY Sds.Osdr.Domain Sds.Osdr.Domain diff --git a/Sds.Osdr.WebApi.IntegrationTests/EndPoints/WebClientHelper.cs b/Sds.Osdr.WebApi.IntegrationTests/EndPoints/WebClientHelper.cs index 0cde92c..bcd6267 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/EndPoints/WebClientHelper.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/EndPoints/WebClientHelper.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json; +using System; using System.Net.Http; using System.Threading.Tasks; @@ -25,8 +26,22 @@ public async Task GetData(string url, string contentType = var response = await client.SendAsync(request); - return response; + return response; } + + public async Task DeleteData(string url) + { + var request = new HttpRequestMessage(new HttpMethod("DELETE"), new Uri(BaseUri, url)); + + request.Content = new StringContent(""); + + request.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + + var response = await client.SendAsync(request); + + return response; + } + public async Task PatchData(string url, string stringContent) { var content = new StringContent(stringContent); @@ -44,7 +59,31 @@ public async Task PatchData(string url, string stringConten return response; } - + + public async Task PutData(string url, string stringContent) + { + var content = new StringContent(stringContent); + + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + + var request = new HttpRequestMessage(new HttpMethod("PUT"), new Uri(BaseUri, url)) + { + Content = content + }; + + HttpResponseMessage response = new HttpResponseMessage(); + + response = await client.SendAsync(request); + + return response; + } + + + public async Task PutData(string url, object data) + { + return await PutData(url, JsonConvert.SerializeObject(data)); + } + public async Task PostData(string url, string data) { var httpContent = new StringContent(data); @@ -53,5 +92,10 @@ public async Task PostData(string url, string data) return response; } + + public async Task PostData(string url, object data) + { + return await PostData(url, JsonConvert.SerializeObject(data)); + } } } \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Extensions/EntititesControllerExtension.cs b/Sds.Osdr.WebApi.IntegrationTests/Extensions/EntititesControllerExtension.cs index 2a9d29f..d90c9c8 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Extensions/EntititesControllerExtension.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Extensions/EntititesControllerExtension.cs @@ -74,7 +74,23 @@ public static async Task SetPublicFileEntity(this OsdrWebCl return await client.PatchData(url, data); } - + + public static async Task SetFileName(this OsdrWebClient client, Guid fileId, int version, string name) + { + var url = $"/api/entities/files/{fileId}?version={version}"; + var data = $"[{{'op':'replace','path':'Name','value':'{name}'}}]"; + + return await client.PatchData(url, data); + } + + public static async Task SetParentFolder(this OsdrWebClient client, Guid fileId, int version, Guid folderId) + { + var url = $"/api/entities/files/{fileId}?version={version}"; + var data = $"[{{'op':'replace','path':'ParentId','value':'{folderId}'}}]"; + + return await client.PatchData(url, data); + } + public static async Task SetPublicFoldersEntity(this OsdrWebClient client, Guid folderId, int version, bool isPublic) { diff --git a/Sds.Osdr.WebApi.IntegrationTests/Extensions/JsonContainsNodesExtension.cs b/Sds.Osdr.WebApi.IntegrationTests/Extensions/JsonContainsNodesExtension.cs deleted file mode 100644 index 62e9f89..0000000 --- a/Sds.Osdr.WebApi.IntegrationTests/Extensions/JsonContainsNodesExtension.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json.Linq; - -namespace Sds.Osdr.WebApi.IntegrationTests.Extensions -{ - public static class JsonContainsNodesExtension - { - public static int ContainsNodes(this JToken nodes, IList internalIds) - { - var countValid = nodes.Count(node => internalIds.Contains(node["id"].ToObject())); - return countValid; - } - } -} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Extensions/JsonExtension.cs b/Sds.Osdr.WebApi.IntegrationTests/Extensions/JsonExtension.cs new file mode 100644 index 0000000..1bdd34c --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Extensions/JsonExtension.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.WebApi.IntegrationTests.EndPoints; + +namespace Sds.Osdr.WebApi.IntegrationTests.Extensions +{ + public static class JsonExtension + { + //should be romeved soon + //public static async Task ReadJsonAsync(this OsdrWebClient client, string url) + //{ + // var response = await client.GetData(url); + // response.EnsureSuccessStatusCode(); + // return await response.Content.ReadAsStringAsync(); + //} + + //public static async Task ReadObjectAsync(this OsdrWebClient client, string url) where T : class + //{ + // var json = await client.ReadJsonAsync(url); + // return JsonConvert.DeserializeObject(json); + //} + // + + public static async Task ReadAsJObjectAsync(this HttpContent content) + { + return JObject.Parse(await content.ReadAsStringAsync()); + } + + public static async Task ReadAsJArrayAsync(this HttpContent content) + { + return JArray.Parse(await content.ReadAsStringAsync()); + } + + public static int ContainsNodes(this JToken nodes, IList internalIds) + { + var countValid = nodes.Count(node => internalIds.Contains(node["id"].ToObject())); + return countValid; + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/KeyCloak/keycloak-settings/osdr-realm.json b/Sds.Osdr.WebApi.IntegrationTests/KeyCloak/keycloak-settings/osdr-realm.json index ab46131..2103840 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/KeyCloak/keycloak-settings/osdr-realm.json +++ b/Sds.Osdr.WebApi.IntegrationTests/KeyCloak/keycloak-settings/osdr-realm.json @@ -44,7 +44,7 @@ { "type" : "password", "value" : "qqq123" } ], - "realmRoles": [ "user" ], + "realmRoles": [ "user", "leanda-admin" ], "clientRoles": { "account": ["view-profile", "manage-account"] } @@ -67,9 +67,22 @@ ], "roles" : { "realm" : [ + { + "id": "a52b7f6f-ccf4-4f48-ac55-38fe9d5509ec", + "name": "leanda-admin", + "description": "Administrator privileges", + "scopeParamRequired": true, + "composite": false, + "clientRole": false, + "containerId": "4ee57060-1936-4b39-b935-ec970d21d920" + } ] }, "scopeMappings": [ + { + "client": "osdr_webapi", + "roles": ["user", "leanda-admin"] + } ], "clients": [ { @@ -164,7 +177,8 @@ "consentText": "${fullName}", "config": { "id.token.claim": "true", - "access.token.claim": "true" + "access.token.claim": "true", + "userinfo.token.claim": "true" } }, { @@ -176,6 +190,7 @@ "consentText": "", "config": { "user.session.note": "clientHost", + "userinfo.token.claim": "true", "id.token.claim": "true", "access.token.claim": "true", "claim.name": "clientHost", @@ -191,6 +206,7 @@ "consentText": "", "config": { "user.session.note": "clientAddress", + "userinfo.token.claim": "true", "id.token.claim": "true", "access.token.claim": "true", "claim.name": "clientAddress", @@ -213,6 +229,21 @@ "jsonType.label": "String" } }, + { + "id": "0db369f9-2d25-4b73-86d8-bae9aa9e6ac4", + "name": "User realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "multivalued": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "user_role", + "jsonType.label": "String" + } + }, { "id": "ca1af84f-2f31-41d6-9b92-c2193882c870", "name": "role list", @@ -234,6 +265,7 @@ "consentText": "", "config": { "user.session.note": "clientId", + "userinfo.token.claim": "true", "id.token.claim": "true", "access.token.claim": "true", "claim.name": "clientId", diff --git a/Sds.Osdr.WebApi.IntegrationTests/Sds.Osdr.WebApi.IntegrationTests.csproj b/Sds.Osdr.WebApi.IntegrationTests/Sds.Osdr.WebApi.IntegrationTests.csproj index 42e1e29..b1a7080 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Sds.Osdr.WebApi.IntegrationTests.csproj +++ b/Sds.Osdr.WebApi.IntegrationTests/Sds.Osdr.WebApi.IntegrationTests.csproj @@ -31,7 +31,7 @@ - + all @@ -40,6 +40,7 @@ + @@ -55,4 +56,8 @@ + + + + diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryEntities/AddEntityCategories.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryEntities/AddEntityCategories.cs new file mode 100644 index 0000000..a1231ca --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryEntities/AddEntityCategories.cs @@ -0,0 +1,84 @@ +using FluentAssertions; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.Traits; +using Sds.Osdr.WebApi.IntegrationTests.Extensions; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests +{ + public class AddEntityCategoriesFixture + { + public Guid CategoryId { get; set; } + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + public Guid FileNodeId { get; set; } + + + public AddEntityCategoriesFixture(OsdrWebTestHarness harness) + { + var categories = new List() + { + new TreeNode("Category Root", new List() + { + new TreeNode("My Test Category"), + new TreeNode("Projects Two") + }) + }; + + var response = harness.JohnApi.PostData("api/categorytrees/tree", categories).Result; + + var content = response.Content.ReadAsStringAsync().Result; + + CategoryId = Guid.Parse(content); + + harness.WaitWhileCategoryTreePersisted(CategoryId); + + BlobId = harness.JohnBlobStorageClient.AddResource(harness.JohnId.ToString(), "Chemical-diagram.png", new Dictionary() { { "parentId", harness.JohnId } }).Result; + + FileId = harness.WaitWhileFileProcessed(BlobId); + + var fileNodeResponse = harness.JohnApi.GetNodeById(FileId).Result; + var fileNode = JsonConvert.DeserializeObject(fileNodeResponse.Content.ReadAsStringAsync().Result); + FileNodeId = Guid.Parse(fileNode.Value("id")); + + // add category to entity + response = harness.JohnApi.PostData($"/api/categoryentities/entities/{FileNodeId}/categories", new List { CategoryId }).Result; + response.EnsureSuccessStatusCode(); + harness.WaitWhileCategoryIndexed(CategoryId.ToString()); + } + } + + [Collection("OSDR Test Harness")] + public class AddEntityCategories : OsdrWebTest, IClassFixture + { + private Guid CategoryId; + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + public Guid FileNodeId { get; set; } + + public AddEntityCategories(OsdrWebTestHarness harness, ITestOutputHelper output, AddEntityCategoriesFixture fixture) : base(harness, output) + { + CategoryId = fixture.CategoryId; + BlobId = fixture.BlobId; + FileId = fixture.FileId; + FileNodeId = fixture.FileNodeId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Folder)] + public async Task AddOneCategoryToEntity() + { + var nodesRequest = await JohnApi.GetData($"/api/categoryentities/categories/{CategoryId}"); + var nodes = await nodesRequest.Content.ReadAsJArrayAsync(); + nodes.Count.Should().Be(1); + nodes[0].Value("id").Should().Be(FileNodeId.ToString()); + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryEntities/DeleteEntityCategory.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryEntities/DeleteEntityCategory.cs new file mode 100644 index 0000000..db3e9d7 --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryEntities/DeleteEntityCategory.cs @@ -0,0 +1,101 @@ +using FluentAssertions; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using Sds.Osdr.WebApi.IntegrationTests.Extensions; +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests +{ + public class DeleteEntityCategoryFixture + { + public Guid RootCategoryId; + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + + + public DeleteEntityCategoryFixture(OsdrWebTestHarness harness) + { + var categories = new List() + { + new TreeNode("Category Root", new List() + { + new TreeNode("My Test Category"), + new TreeNode("Projects Two") + }) + }; + + var response = harness.JohnApi.PostData("api/categorytrees/tree", categories).Result; + + var content = response.Content.ReadAsStringAsync().Result; + + RootCategoryId = Guid.Parse(content); + + harness.WaitWhileCategoryTreePersisted(RootCategoryId); + + BlobId = harness.JohnBlobStorageClient.AddResource(harness.JohnId.ToString(), "Chemical-diagram.png", new Dictionary() { { "parentId", harness.JohnId } }).Result; + + FileId = harness.WaitWhileFileProcessed(BlobId); + } + } + + [Collection("OSDR Test Harness")] + public class DeleteEntityCategory : OsdrWebTest, IClassFixture + { + private Guid RootCategoryId; + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + + + public DeleteEntityCategory(OsdrWebTestHarness harness, ITestOutputHelper output, DeleteEntityCategoryFixture fixture) : base(harness, output) + { + RootCategoryId = fixture.RootCategoryId; + BlobId = fixture.BlobId; + FileId = fixture.FileId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task DeleteOneCategoryToEntity() + { + var fileNodeResponse = await JohnApi.GetNodeById(FileId); + var fileNode = await fileNodeResponse.Content.ReadAsJObjectAsync(); + var fileNodeId = Guid.Parse(fileNode.Value("id")); + + var treeResponse = await JohnApi.GetData($"api/categorytrees/tree/{RootCategoryId}"); + var treeContent = await treeResponse.Content.ReadAsJObjectAsync(); + var categoryId1 = treeContent["nodes"][0]["children"][0]["id"].ToString(); + var categoryId2 = treeContent["nodes"][0]["children"][1]["id"].ToString(); + + // add categories to entity + await JohnApi.PostData($"/api/categoryentities/entities/{fileNodeId}/categories", new List { categoryId1, categoryId2 }); + WebFixture.WaitWhileCategoryIndexed(categoryId1.ToString()); + WebFixture.WaitWhileCategoryIndexed(categoryId2.ToString()); + // check if node exists by categoryId1 + var firstCategoryAddedNodeRequest = await JohnApi.GetData($"/api/categoryentities/categories/{categoryId1}"); + var firstCategoryAddedNode = await firstCategoryAddedNodeRequest.Content.ReadAsJArrayAsync(); + firstCategoryAddedNode.First().Value("id").Should().Be(fileNodeId.ToString()); + + // delete first category from node + await JohnApi.DeleteData($"/api/categoryentities/entities/{fileNodeId}/categories/{categoryId1}"); + WebFixture.WaitWhileCategoryDeleted(categoryId1.ToString()); + // check if node contains categoryId1 + var firstCategoryDeletedNodeRequest = await JohnApi.GetData($"/api/categoryentities/categories/{categoryId1}"); + var firstCategoryDeletedNode = await firstCategoryDeletedNodeRequest.Content.ReadAsJArrayAsync(); + firstCategoryDeletedNode.Should().BeEmpty(); + + var secondCategoryAddedNodeRequest = await JohnApi.GetData($"/api/categoryentities/categories/{categoryId2}"); + var secondCategoryAddedNode = await secondCategoryAddedNodeRequest.Content.ReadAsJArrayAsync(); + secondCategoryAddedNode.First().Value("id").Should().Be(fileNodeId.ToString()); + } + } +} diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryEntities/GetCategoriesIdsByEntityId.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryEntities/GetCategoriesIdsByEntityId.cs new file mode 100644 index 0000000..8e13636 --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryEntities/GetCategoriesIdsByEntityId.cs @@ -0,0 +1,83 @@ +using FluentAssertions; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using Sds.Osdr.WebApi.IntegrationTests.Extensions; +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Threading; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests +{ + public class GetCategoriesIdsByEntityIdFixture + { + public Guid CategoryId; + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + + + public GetCategoriesIdsByEntityIdFixture(OsdrWebTestHarness harness) + { + var categories = new List() + { + new TreeNode("Category Root", new List() + { + new TreeNode("My Test Category"), + new TreeNode("Projects Two") + }) + }; + + var response = harness.JohnApi.PostData("api/categorytrees/tree", categories).Result; + + var content = response.Content.ReadAsStringAsync().Result; + + CategoryId = Guid.Parse(content); + + harness.WaitWhileCategoryTreePersisted(CategoryId); + + BlobId = harness.JohnBlobStorageClient.AddResource(harness.JohnId.ToString(), "Chemical-diagram.png", new Dictionary() { { "parentId", harness.JohnId } }).Result; + + FileId = harness.WaitWhileFileProcessed(BlobId); + + } + } + + [Collection("OSDR Test Harness")] + public class GetCategoriesIdsByEntityId : OsdrWebTest, IClassFixture + { + private Guid CategoryId; + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + + + public GetCategoriesIdsByEntityId(OsdrWebTestHarness harness, ITestOutputHelper output, GetCategoriesIdsByEntityIdFixture fixture) : base(harness, output) + { + CategoryId = fixture.CategoryId; + BlobId = fixture.BlobId; + FileId = fixture.FileId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task GetCategoriesIdsByEntityIdTest() + { + var fileNodeResponse = await JohnApi.GetNodeById(FileId); + var fileNode = await fileNodeResponse.Content.ReadAsJObjectAsync(); + var fileNodeId = Guid.Parse(fileNode.Value("id")); + + await JohnApi.PostData($"/api/categoryentities/entities/{fileNodeId}/categories", new List { CategoryId }); + WebFixture.WaitWhileCategoryIndexed(CategoryId.ToString()); + + var response = await JohnApi.GetData($"/api/categoryentities/entities/{fileNodeId}/categories"); + var categoriesIds = await response.Content.ReadAsJArrayAsync(); + categoriesIds.Single().Value().Should().Be(CategoryId.ToString()); + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/CreateAndGetCategoryTree.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/CreateAndGetCategoryTree.cs new file mode 100644 index 0000000..d678981 --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/CreateAndGetCategoryTree.cs @@ -0,0 +1,79 @@ +using FluentAssertions; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests +{ + public class CreateAndGetCategoryTreeFixture + { + public Guid CategoryId; + + public CreateAndGetCategoryTreeFixture(OsdrWebTestHarness harness) + { + var categories = new List() + { + new TreeNode("Projects", new List() + { + new TreeNode("Projects One"), + new TreeNode("Projects Two") + }) + }; + + var response = harness.JohnApi.PostData("/api/categorytrees/tree", categories).Result; + + var content = response.Content.ReadAsStringAsync().Result; + + CategoryId = Guid.Parse(content); + + harness.WaitWhileCategoryTreePersisted(CategoryId); + } + } + + [Collection("OSDR Test Harness")] + public class CreateAndGetCategoryTree : OsdrWebTest, IClassFixture + { + private Guid CategoryId; + + public CreateAndGetCategoryTree(OsdrWebTestHarness harness, ITestOutputHelper output, CreateAndGetCategoryTreeFixture fixture) : base(harness, output) + { + CategoryId = fixture.CategoryId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTree_CreateNewCategoryTree_BuiltExpectedDocument() + { + var response = await JohnApi.GetData($"/api/categorytrees/tree/{CategoryId}"); + response.EnsureSuccessStatusCode(); + + var content = await response.Content.ReadAsStringAsync(); + var jsonCategory = JToken.Parse(await response.Content.ReadAsStringAsync()); + + jsonCategory.Should().ContainsJson($@" + {{ + 'id': '{CategoryId}', + 'createdBy': '{JohnId}', + 'createdDateTime': *EXIST*, + 'updatedBy': '{JohnId}', + 'updatedDateTime': *EXIST*, + 'version': 1, + 'nodes': *EXIST* + }}"); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTree_GetNonExistantCategoryTree_ReturnsNotFoundCode() + { + var response = await JohnApi.GetData($"/api/categorytrees/tree/{Guid.NewGuid()}"); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/CreateCategoryTreeWithoutAdminPermissions.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/CreateCategoryTreeWithoutAdminPermissions.cs new file mode 100644 index 0000000..aa037bf --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/CreateCategoryTreeWithoutAdminPermissions.cs @@ -0,0 +1,88 @@ +using FluentAssertions; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests +{ + + [Collection("OSDR Test Harness")] + public class CreateCategoryTreeWithoutAdminPermissions : OsdrWebTest, IClassFixture + { + private Guid CategoryId; + + public CreateCategoryTreeWithoutAdminPermissions(OsdrWebTestHarness harness, ITestOutputHelper output, CreateAndGetCategoryTreeFixture fixture) : base(harness, output) + { + CategoryId = fixture.CategoryId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTree_CreateNewCategoryTree_ReturnsForbidden() + { + var categories = new List() + { + new TreeNode("Projects", new List() + { + new TreeNode("Projects One"), + new TreeNode("Projects Two") + }) + }; + + var response = await JaneApi.PostData("/api/categorytrees/tree", categories); + response.StatusCode.Should().Be(HttpStatusCode.Forbidden); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTree_UpdateCategoryTree_ReturnsForbidden() + { + var categories = new List() + { + new TreeNode("Projects", new List() + { + new TreeNode("Projects One"), + new TreeNode("Projects Two") + }) + }; + + var response = await JaneApi.PutData($"/api/categorytrees/tree/{CategoryId}", categories); + response.StatusCode.Should().Be(HttpStatusCode.Forbidden); + + response = await JaneApi.PutData($"/api/categorytrees/tree/{CategoryId}/{Guid.NewGuid()}", categories); + response.StatusCode.Should().Be(HttpStatusCode.Forbidden); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTree_DeleteCategoryTree_ReturnsForbidden() + { + var response = await JaneApi.DeleteData($"/api/categorytrees/tree/{CategoryId}"); + response.StatusCode.Should().Be(HttpStatusCode.Forbidden); + + response = await JaneApi.DeleteData($"/api/categorytrees/tree/{CategoryId}/{Guid.NewGuid()}"); + response.StatusCode.Should().Be(HttpStatusCode.Forbidden); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTree_UpdateNodeFromCategoryTree_ReturnsForbidden() + { + var categories = new List() + { + new TreeNode("Projects", new List() + { + new TreeNode("Projects One"), + new TreeNode("Projects Two") + }) + }; + + var response = await JaneApi.PutData($"/api/categorytrees/tree/{CategoryId}/{Guid.NewGuid()}", categories); + response.StatusCode.Should().Be(HttpStatusCode.Forbidden); + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/DeleteCategoryTree.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/DeleteCategoryTree.cs new file mode 100644 index 0000000..4660dc8 --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/DeleteCategoryTree.cs @@ -0,0 +1,61 @@ +using FluentAssertions; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests +{ + public class DeleteCategoryFixture + { + public Guid CategoryId { get; } + + public DeleteCategoryFixture(OsdrWebTestHarness harness) + { + var categories = new List() + { + new TreeNode(Guid.NewGuid(), "Projects", new List() + { + new TreeNode(Guid.NewGuid(), "Projects One"), + new TreeNode(Guid.NewGuid(), "Projects Two") + }) + }; + + var response = harness.JohnApi.PostData("/api/categorytrees/tree", categories).Result; + + var content = response.Content.ReadAsStringAsync().Result; + + CategoryId = Guid.Parse(content); + + harness.WaitWhileCategoryTreePersisted(CategoryId); + } + } + + [Collection("OSDR Test Harness")] + public class DeleteCategoryTree : OsdrWebTest, IClassFixture + { + private Guid CategoryId; + + public DeleteCategoryTree(OsdrWebTestHarness harness, ITestOutputHelper output, DeleteCategoryFixture fixture) : base(harness, output) + { + CategoryId = fixture.CategoryId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Folder)] + public async Task CategoryTreeOperations_DeleteCategoryTree_ExpectedUpdatedCategory() + { + var response = await JohnApi.DeleteData($"/api/categorytrees/tree/{CategoryId}"); + response.EnsureSuccessStatusCode(); + Harness.WaitWhileCategoryTreeDeletePersisted(CategoryId); + + response = await JohnApi.GetData($"/api/categorytrees/tree/{CategoryId}"); + response.StatusCode.Should().Be(System.Net.HttpStatusCode.NotFound); + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/DeleteCategoryTreeViaPatch.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/DeleteCategoryTreeViaPatch.cs new file mode 100644 index 0000000..fafe5f3 --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/DeleteCategoryTreeViaPatch.cs @@ -0,0 +1,39 @@ +using FluentAssertions; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests +{ + [Collection("OSDR Test Harness")] + public class DeleteCategoryTreeViaPatch : OsdrWebTest, IClassFixture + { + private Guid CategoryId; + + public DeleteCategoryTreeViaPatch(OsdrWebTestHarness harness, ITestOutputHelper output, DeleteCategoryFixture fixture) : base(harness, output) + { + CategoryId = fixture.CategoryId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Folder)] + public async Task CategoryTreeOperations_DeleteCategoryTree_ExpectedUpdatedCategory() + { + var url = $"/api/categorytrees/tree/{CategoryId}"; + var data = $"[{{'op':'replace','path':'isDeleted','value':true}}]"; + + var response = await JohnApi.PatchData(url, data); + response.EnsureSuccessStatusCode(); + Harness.WaitWhileCategoryTreeDeletePersisted(CategoryId); + + response = await JohnApi.GetData($"/api/categorytrees/tree/{CategoryId}"); + response.StatusCode.Should().Be(System.Net.HttpStatusCode.NotFound); + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/DeleteNodeFromCategoryTree.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/DeleteNodeFromCategoryTree.cs new file mode 100644 index 0000000..45044f1 --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/DeleteNodeFromCategoryTree.cs @@ -0,0 +1,72 @@ +using FluentAssertions; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using Sds.Osdr.WebApi.IntegrationTests.EndPoints; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests +{ + [Collection("OSDR Test Harness")] + public class DeleteNodeFromCategoryTree : OsdrWebTest, IClassFixture + { + private Guid CategoryId; + private Guid ChildCategoryId; + + public DeleteNodeFromCategoryTree(OsdrWebTestHarness harness, ITestOutputHelper output, DeleteCategoryFixture fixture) : base(harness, output) + { + CategoryId = fixture.CategoryId; + ChildCategoryId = GetNodeIdsForCategory(harness.JohnApi, CategoryId).Result.Last(); + } + + private async Task> GetNodeIdsForCategory(OsdrWebClient client, Guid categoryId) + { + var response = await client.GetData($"/api/categorytrees/tree/{categoryId}"); + response.EnsureSuccessStatusCode(); + var json = await response.Content.ReadAsStringAsync(); + json = json.Replace("_id", "id"); + var treeJson = JsonConvert.DeserializeObject>(json)["nodes"].ToString(); + var tree = JsonConvert.DeserializeObject>(treeJson); + return tree.GetNodeIds(); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTreeOperations_DeleteNodeFromCategoryTree_ExpectedCategoryWithoutDeletedCategory() + { + //will be sure that we`re deleting node which exists + var nodesIds = await GetNodeIdsForCategory(JohnApi, CategoryId); + nodesIds.Should().Contain(ChildCategoryId); + + var response = await JohnApi.DeleteData($"/api/categorytrees/tree/{CategoryId}/{ChildCategoryId}"); + response.EnsureSuccessStatusCode(); + Harness.WaitWhileCategoryTreeNodeDeletePersisted(ChildCategoryId); + + //And now we got rid from it + nodesIds = await GetNodeIdsForCategory(JohnApi, CategoryId); + nodesIds.Should().NotContain(ChildCategoryId); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTreeOperations_DeleteNonExistantNodeFromCategoryTree_ReturnsNotFoundCode() + { + var response = await JohnApi.DeleteData($"/api/categorytrees/tree/{CategoryId}/{Guid.NewGuid()}"); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTreeOperations_DeleteNodeFromNonExistantCategoryTree_ReturnsNotFoundCode() + { + var response = await JohnApi.DeleteData($"/api/categorytrees/tree/{Guid.NewGuid()}/{ChildCategoryId}"); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/DeleteNodeFromCategoryTreeViaPatch.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/DeleteNodeFromCategoryTreeViaPatch.cs new file mode 100644 index 0000000..474785f --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/DeleteNodeFromCategoryTreeViaPatch.cs @@ -0,0 +1,81 @@ +using FluentAssertions; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using Sds.Osdr.WebApi.IntegrationTests.EndPoints; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests +{ + [Collection("OSDR Test Harness")] + public class DeleteNodeFromCategoryTreeViaPatch : OsdrWebTest, IClassFixture + { + private Guid CategoryId; + private Guid ChildCategoryId; + + public DeleteNodeFromCategoryTreeViaPatch(OsdrWebTestHarness harness, ITestOutputHelper output, DeleteCategoryFixture fixture) : base(harness, output) + { + CategoryId = fixture.CategoryId; + ChildCategoryId = GetNodeIdsForCategory(harness.JohnApi, CategoryId).Result.Last(); + } + + private async Task> GetNodeIdsForCategory(OsdrWebClient client, Guid categoryId) + { + var response = await client.GetData($"/api/categorytrees/tree/{categoryId}"); + response.EnsureSuccessStatusCode(); + var json = await response.Content.ReadAsStringAsync(); + json = json.Replace("_id", "id"); + var treeJson = JsonConvert.DeserializeObject>(json)["nodes"].ToString(); + var tree = JsonConvert.DeserializeObject>(treeJson); + return tree.GetNodeIds(); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTreeOperations_DeleteNodeFromCategoryTree_ExpectedCategoryWithoutDeletedCategory() + { + //will be sure that we`re deleting node which exists + var nodesIds = await GetNodeIdsForCategory(JohnApi, CategoryId); + nodesIds.Should().Contain(ChildCategoryId); + + var url = $"/api/categorytrees/tree/{CategoryId}/{ChildCategoryId}"; + var data = $"[{{'op':'replace','path':'isDeleted','value': true}}]"; + + var response = await JohnApi.PatchData(url, data); + response.EnsureSuccessStatusCode(); + Harness.WaitWhileCategoryTreeNodeDeletePersisted(ChildCategoryId); + + //And now we got rid from it + nodesIds = await GetNodeIdsForCategory(JohnApi, CategoryId); + nodesIds.Should().NotContain(ChildCategoryId); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTreeOperations_DeleteNonExistantNodeFromCategoryTree_ReturnsNotFoundCode() + { + var url = $"/api/categorytrees/tree/{CategoryId}/{Guid.NewGuid()}"; + var data = $"[{{'op':'replace','path':'isDeleted','value': true}}]"; + + var response = await JohnApi.PatchData(url, data); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTreeOperations_DeleteNodeFromNonExistantCategoryTree_ReturnsNotFoundCode() + { + var url = $"/api/categorytrees/tree/{Guid.NewGuid()}/{ChildCategoryId}"; + var data = $"[{{'op':'replace','path':'isDeleted','value': true}}]"; + + var response = await JohnApi.PatchData(url, data); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/GetAllCategoryTrees.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/GetAllCategoryTrees.cs new file mode 100644 index 0000000..2b73fbc --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/GetAllCategoryTrees.cs @@ -0,0 +1,64 @@ +using FluentAssertions; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests +{ + [Collection("OSDR Test Harness")] + public class GetAllCategoryTrees : OsdrWebTest + { + public GetAllCategoryTrees(OsdrWebTestHarness fixture, ITestOutputHelper output) : base(fixture, output) + { + var categories = new List() + { + new TreeNode("Projects", new List() + { + new TreeNode("Projects One"), + new TreeNode("Projects Two") + }) + }; + + for (int i = 0; i < 10; i++) + { + var response = JohnApi.PostData("/api/categorytrees/tree", categories).Result; + + var content = response.Content.ReadAsStringAsync().Result; + + var categoryId = Guid.Parse(content); + + Harness.WaitWhileCategoryTreePersisted(categoryId); + } + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTreeOperations_GetAllCategoryTrees_ExpectedListOfCategories() + { + var response = await JohnApi.GetData($"/api/categorytrees/tree"); + response.EnsureSuccessStatusCode(); + + var content = await response.Content.ReadAsStringAsync(); + var jsonCategories = JArray.Parse(await response.Content.ReadAsStringAsync()); + jsonCategories.Should().NotBeEmpty(); + foreach (var category in jsonCategories.Children()) + { + category.Should().ContainsJson($@" + {{ + 'id': *EXIST*, + 'createdBy': '{JohnId}', + 'createdDateTime': *EXIST*, + 'updatedBy': '{JohnId}', + 'updatedDateTime': *EXIST*, + 'version': *EXIST* + }}"); + } + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/UpdateCategoryTree.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/UpdateCategoryTree.cs new file mode 100644 index 0000000..bc8f634 --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/UpdateCategoryTree.cs @@ -0,0 +1,157 @@ +using FluentAssertions; +using FluentAssertions.Json; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using Sds.Osdr.WebApi.IntegrationTests.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests +{ + public class UpdateCategoryTreeFixture + { + public Guid CategoryId; + + public UpdateCategoryTreeFixture(OsdrWebTestHarness harness) + { + var categories = new List() + { + new TreeNode("Projects", new List() + { + new TreeNode("Projects One"), + new TreeNode("Projects Two") + }) + }; + + var response = harness.JohnApi.PostData("/api/categorytrees/tree", categories).Result; + + var content = response.Content.ReadAsStringAsync().Result; + + CategoryId = Guid.Parse(content); + + harness.WaitWhileCategoryTreePersisted(CategoryId); + + var nodeIds = GetNodeIdsForCategory(harness, CategoryId).Result.ToList(); + + var guidOne = nodeIds.ElementAt(0); + var guidTwo = nodeIds.ElementAt(1); + var guidThree = nodeIds.ElementAt(2); + + var json = $@"[ + {{ + 'id': '{guidOne}', + 'title': 'Level 0: Main Node 1', + 'children': [ + {{ 'id': '{guidTwo}', 'title': 'Level 1: Node 1', 'children': null }}, + {{ 'id': '{guidThree}', 'title': 'Level 1: Node 2', 'children': null }} + ] + }}, + {{ 'title': 'NoNameNode' }}, + {{ 'title': '1' }}, + {{ 'title': '2' }}, + {{ 'title': '3' }}, + {{ 'title': '4', 'children': [{{ 'title': '4-1' }}, {{ 'title': '4-2', 'children': [{{ 'title': '4-2-1' }}] }}] }} + ]"; + categories = JsonConvert.DeserializeObject>(json); + + response = harness.JohnApi.PutData($"/api/categorytrees/tree/{CategoryId}", categories).Result; + + harness.WaitWhileCategoryTreeUpdatedPersisted(CategoryId); + } + + private async Task> GetNodeIdsForCategory(OsdrWebTestHarness harness, Guid categoryId) + { + var response = await harness.JohnApi.GetData($"/api/categorytrees/tree/{categoryId}"); + response.EnsureSuccessStatusCode(); + var json = await response.Content.ReadAsStringAsync(); + json = json.Replace("_id", "id"); + var treeJson = JsonConvert.DeserializeObject>(json)["nodes"].ToString(); + var tree = JsonConvert.DeserializeObject>(treeJson); + return tree.GetNodeIds(); + } + } + + [Collection("OSDR Test Harness")] + public class UpdateCategoryTree : OsdrWebTest, IClassFixture + { + private Guid CategoryId; + + public UpdateCategoryTree(OsdrWebTestHarness harness, ITestOutputHelper output, UpdateCategoryTreeFixture fixture) : base(harness, output) + { + CategoryId = fixture.CategoryId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTree_UpdateExistantCategoryTree_BuiltExpectedDocument() + { + var contentRequest = await JohnApi.GetData($"/api/categorytrees/tree/{CategoryId}"); + + var jsonCategory = await contentRequest.Content.ReadAsJObjectAsync(); + + jsonCategory.Should().HaveElement("id"); + jsonCategory["id"].Value().Should().Be(CategoryId.ToString()); + + jsonCategory.Should().HaveElement("createdBy"); + jsonCategory["createdBy"].Value().Should().Be(JohnId.ToString()); + + jsonCategory.Should().HaveElement("createdDateTime") + .And.HaveElement("createdDateTime") + .And.HaveElement("updatedDateTime"); + + jsonCategory.Should().HaveElement("version"); + jsonCategory["version"].Value().Should().Be(2); + + jsonCategory.Should().HaveElement("nodes"); + var treeNodes = jsonCategory["nodes"].Value(); + treeNodes.Should().HaveCount(6); + treeNodes.Select(i => i.Should().HaveElement("id")); + var titles = treeNodes.Select(i => i["title"].Value()); + titles.Should().Contain(new List { "Level 0: Main Node 1", "NoNameNode", "1", "2", "3", "4"}); + var firstNode = treeNodes.Where(i => i.Value("title") == "Level 0: Main Node 1").SingleOrDefault(); + firstNode.Should().NotBeNull(); + firstNode.Should().HaveElement("title"); + var insideNodes = firstNode["children"].Value(); + insideNodes.Should().HaveCount(2); + var insideTitles = insideNodes.Select(i => i["title"].Value()); + insideTitles.Should().Contain(new List { "Level 1: Node 1", "Level 1: Node 2" }); + insideNodes.Select(i => i.Should().HaveElement("id")); + + var lastNode = treeNodes.Where(i => i.Value("title") == "4").SingleOrDefault(); + lastNode.Should().NotBeNull(); + var lastNodeInsideNodes = lastNode["children"].Value(); + lastNodeInsideNodes.Should().HaveCount(2); + var lastNodeInsideTitles = lastNodeInsideNodes.Select(i => i["title"].Value()); + lastNodeInsideTitles.Should().Contain(new List { "4-1", "4-2" }); + lastNodeInsideNodes.Select(i => i.Should().HaveElement("id")); + + var lastNodeSubnode = lastNodeInsideNodes.Where(i => i.Value("title") == "4-2").SingleOrDefault(); + lastNodeSubnode.Should().NotBeNull(); + var lastSubnodeChildren = lastNodeSubnode["children"].Value(); + lastSubnodeChildren.Should().HaveCount(1); + lastSubnodeChildren.Single().Should().HaveElement("id"); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTree_UpdateNonExistantCategoryTree_ReturnsNotFoundCode() + { + var response = await JohnApi.PutData($"/api/categorytrees/tree/{Guid.NewGuid()}", new List() + { + new TreeNode("Projects", new List() + { + new TreeNode("Projects One"), + new TreeNode("Projects Two") + }) + }); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/UpdateCategoryTreeNode.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/UpdateCategoryTreeNode.cs new file mode 100644 index 0000000..b0ecf62 --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/UpdateCategoryTreeNode.cs @@ -0,0 +1,119 @@ +using FluentAssertions; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests +{ + public class UpdateCategoryTreeNodeFixture + { + public Guid CategoryId; + + public Guid NodeId; + + public UpdateCategoryTreeNodeFixture(OsdrWebTestHarness harness) + { + var categories = new List() + { + new TreeNode("Projects", new List() + { + new TreeNode("Projects One"), + new TreeNode("Projects Two") + }) + }; + + var response = harness.JohnApi.PostData("/api/categorytrees/tree", categories).Result; + + var content = response.Content.ReadAsStringAsync().Result; + + CategoryId = Guid.Parse(content); + + harness.WaitWhileCategoryTreePersisted(CategoryId); + + var nodeIds = GetNodeIdsForCategory(harness, CategoryId).Result.ToList(); + + NodeId = nodeIds.Last(); + } + + private async Task> GetNodeIdsForCategory(OsdrWebTestHarness harness, Guid categoryId) + { + var response = await harness.JohnApi.GetData($"/api/categorytrees/tree/{categoryId}"); + response.EnsureSuccessStatusCode(); + var json = await response.Content.ReadAsStringAsync(); + json = json.Replace("_id", "id"); + var treeJson = JsonConvert.DeserializeObject>(json)["nodes"].ToString(); + var tree = JsonConvert.DeserializeObject>(treeJson); + return tree.GetNodeIds(); + } + } + + [Collection("OSDR Test Harness")] + public class UpdateCategoryTreeNode : OsdrWebTest, IClassFixture + { + private Guid CategoryId; + private Guid NodeId; + private List Categories; + + public UpdateCategoryTreeNode(OsdrWebTestHarness harness, ITestOutputHelper output, UpdateCategoryTreeNodeFixture fixture) : base(harness, output) + { + CategoryId = fixture.CategoryId; + NodeId = fixture.NodeId; + Categories = JsonConvert.DeserializeObject>($@"[ + {{ + 'title': 'Level 0: Main Node 1', + 'children': [ + {{ 'title': 'Level 1: Node 1' }}, + {{ 'title': 'Level 1: Node 2' }} + ] + }} + ]"); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTree_UpdateCategoryTreeNode_UpdatedCategoryMayBeExpectedDocument() + { + var response = await JohnApi.PutData($"/api/categorytrees/tree/{CategoryId}/{NodeId}", Categories); + + Harness.WaitWhileCategoryTreeUpdatedPersisted(CategoryId); + + response = await JohnApi.GetData($"/api/categorytrees/tree/{CategoryId}"); + response.EnsureSuccessStatusCode(); + + var jsonCategory = JToken.Parse(await response.Content.ReadAsStringAsync()); + jsonCategory.Should().ContainsJson($@" + {{ + 'id': '{CategoryId}', + 'createdBy': '{JohnId}', + 'createdDateTime': *EXIST*, + 'updatedBy': '{JohnId}', + 'updatedDateTime': *EXIST*, + 'version': 2, + 'nodes': *EXIST* + }}"); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTree_UpdateCategoryTreeWithNonExistantNode_ReturnsNotFound() + { + var response = await JohnApi.PutData($"/api/categorytrees/tree/{CategoryId}/{Guid.NewGuid()}", Categories); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTree_UpdateNodeInNonExistantCategoryTree_ReturnsNotFound() + { + var response = await JohnApi.PutData($"/api/categorytrees/tree/{Guid.NewGuid()}/{NodeId}", Categories); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/UpdateCategoryTreeNodeViaPatch.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/UpdateCategoryTreeNodeViaPatch.cs new file mode 100644 index 0000000..3758e13 --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/UpdateCategoryTreeNodeViaPatch.cs @@ -0,0 +1,66 @@ +using FluentAssertions; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests +{ + [Collection("OSDR Test Harness")] + public class UpdateCategoryTreeNodeViaPatch : OsdrWebTest, IClassFixture + { + private Guid CategoryId; + private Guid NodeId; + + public UpdateCategoryTreeNodeViaPatch(OsdrWebTestHarness harness, ITestOutputHelper output, UpdateCategoryTreeNodeFixture fixture) : base(harness, output) + { + CategoryId = fixture.CategoryId; + NodeId = fixture.NodeId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTree_UpdateCategoryTreeNode_UpdatedCategoryMayBeExpectedDocument() + { + var json = $@"[ + {{ + 'title': 'Level 0: Main Node 1', + 'children': [ + {{ 'title': 'Level 1: Node 1' }}, + {{ 'title': 'Level 1: Node 2' }} + ] + }} + ]"; + + var url = $"/api/categorytrees/tree/{CategoryId}"; + var data = $"[{{'op':'replace','path':'nodes','value': {json} }}]"; + + var response = await JohnApi.PatchData(url, data); + + Harness.WaitWhileCategoryTreeUpdatedPersisted(CategoryId); + + response = await JohnApi.GetData($"/api/categorytrees/tree/{CategoryId}"); + response.EnsureSuccessStatusCode(); + + var jsonCategory = JToken.Parse(await response.Content.ReadAsStringAsync()); + jsonCategory.Should().ContainsJson($@" + {{ + 'id': '{CategoryId}', + 'createdBy': '{JohnId}', + 'createdDateTime': *EXIST*, + 'updatedBy': '{JohnId}', + 'updatedDateTime': *EXIST*, + 'version': 2, + 'nodes': *EXIST* + }}"); + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/UpdateCategoryTreeViaPatch.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/UpdateCategoryTreeViaPatch.cs new file mode 100644 index 0000000..ded8ee3 --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/CategoryTrees/UpdateCategoryTreeViaPatch.cs @@ -0,0 +1,144 @@ +using FluentAssertions; +using FluentAssertions.Json; +using Leanda.Categories.Domain.ValueObjects; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using Sds.Osdr.WebApi.IntegrationTests.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests +{ + public class UpdateCategoryTreeViaPatchFixture + { + public Guid CategoryId; + + public UpdateCategoryTreeViaPatchFixture(OsdrWebTestHarness harness) + { + var categories = new List() + { + new TreeNode("Projects", new List() + { + new TreeNode("Projects One"), + new TreeNode("Projects Two") + }) + }; + + var response = harness.JohnApi.PostData("/api/categorytrees/tree", categories).Result; + + var content = response.Content.ReadAsStringAsync().Result; + + CategoryId = Guid.Parse(content); + + harness.WaitWhileCategoryTreePersisted(CategoryId); + + var nodeIds = GetNodeIdsForCategory(harness, CategoryId).Result.ToList(); + + var guidOne = nodeIds.ElementAt(0); + var guidTwo = nodeIds.ElementAt(1); + var guidThree = nodeIds.ElementAt(2); + + var json = $@"[ + {{ + 'id': '{guidOne}', + 'title': 'Level 0: Main Node 1', + 'children': [ + {{ 'id': '{guidTwo}', 'title': 'Level 1: Node 1', 'children': null }}, + {{ 'id': '{guidThree}', 'title': 'Level 1: Node 2', 'children': null }} + ] + }}, + {{ 'title': 'NoNameNode' }}, + {{ 'title': '1' }}, + {{ 'title': '2' }}, + {{ 'title': '3' }}, + {{ 'title': '4', 'children': [{{ 'title': '4-1' }}, {{ 'title': '4-2', 'children': [{{ 'title': '4-2-1' }}] }}] }} + ]"; + + var url = $"/api/categorytrees/tree/{CategoryId}"; + var data = $"[{{'op':'replace','path':'nodes','value': {json} }}]"; + + response = harness.JohnApi.PatchData(url, data).Result; + harness.WaitWhileCategoryTreeUpdatedPersisted(CategoryId); + } + + private async Task> GetNodeIdsForCategory(OsdrWebTestHarness harness, Guid categoryId) + { + var response = await harness.JohnApi.GetData($"/api/categorytrees/tree/{categoryId}"); + response.EnsureSuccessStatusCode(); + var json = await response.Content.ReadAsStringAsync(); + json = json.Replace("_id", "id"); + var treeJson = JsonConvert.DeserializeObject>(json)["nodes"].ToString(); + var tree = JsonConvert.DeserializeObject>(treeJson); + return tree.GetNodeIds(); + } + } + + [Collection("OSDR Test Harness")] + public class UpdateCategoryTreeViaPatch : OsdrWebTest, IClassFixture + { + private Guid CategoryId; + + public UpdateCategoryTreeViaPatch(OsdrWebTestHarness harness, ITestOutputHelper output, UpdateCategoryTreeViaPatchFixture fixture) : base(harness, output) + { + CategoryId = fixture.CategoryId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Categories)] + public async Task CategoryTree_UpdateExistantCategoryTree_BuiltExpectedDocument() + { + var contentRequest = await JohnApi.GetData($"/api/categorytrees/tree/{CategoryId}"); + + var jsonCategory = await contentRequest.Content.ReadAsJObjectAsync(); + + jsonCategory.Should().HaveElement("id"); + jsonCategory["id"].Value().Should().Be(CategoryId.ToString()); + + jsonCategory.Should().HaveElement("createdBy"); + jsonCategory["createdBy"].Value().Should().Be(JohnId.ToString()); + + jsonCategory.Should().HaveElement("createdDateTime") + .And.HaveElement("createdDateTime") + .And.HaveElement("updatedDateTime"); + + jsonCategory.Should().HaveElement("version"); + jsonCategory["version"].Value().Should().Be(2); + + jsonCategory.Should().HaveElement("nodes"); + var treeNodes = jsonCategory["nodes"].Value(); + treeNodes.Should().HaveCount(6); + treeNodes.Select(i => i.Should().HaveElement("id")); + var titles = treeNodes.Select(i => i["title"].Value()); + titles.Should().Contain(new List { "Level 0: Main Node 1", "NoNameNode", "1", "2", "3", "4"}); + var firstNode = treeNodes.Where(i => i.Value("title") == "Level 0: Main Node 1").SingleOrDefault(); + firstNode.Should().NotBeNull(); + firstNode.Should().HaveElement("title"); + var insideNodes = firstNode["children"].Value(); + insideNodes.Should().HaveCount(2); + var insideTitles = insideNodes.Select(i => i["title"].Value()); + insideTitles.Should().Contain(new List { "Level 1: Node 1", "Level 1: Node 2" }); + insideNodes.Select(i => i.Should().HaveElement("id")); + + var lastNode = treeNodes.Where(i => i.Value("title") == "4").SingleOrDefault(); + lastNode.Should().NotBeNull(); + var lastNodeInsideNodes = lastNode["children"].Value(); + lastNodeInsideNodes.Should().HaveCount(2); + var lastNodeInsideTitles = lastNodeInsideNodes.Select(i => i["title"].Value()); + lastNodeInsideTitles.Should().Contain(new List { "4-1", "4-2" }); + lastNodeInsideNodes.Select(i => i.Should().HaveElement("id")); + + var lastNodeSubnode = lastNodeInsideNodes.Where(i => i.Value("title") == "4-2").SingleOrDefault(); + lastNodeSubnode.Should().NotBeNull(); + var lastSubnodeChildren = lastNodeSubnode["children"].Value(); + lastSubnodeChildren.Should().HaveCount(1); + lastSubnodeChildren.Single().Should().HaveElement("id"); + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/Faileds/Users/Get_invalid_user_info_using_entities.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/Faileds/Users/Get_invalid_user_info_using_entities.cs index b8e9cb2..268c456 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/Faileds/Users/Get_invalid_user_info_using_entities.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/Faileds/Users/Get_invalid_user_info_using_entities.cs @@ -19,8 +19,8 @@ public UnauthorizedGetUserInfoUsingEntities(OsdrWebTestHarness fixture, ITestOut public async Task WebApi_GetUserInfoUsingEntitiesEndpoint_ReturnsError() { var response = await UnauthorizedApi.GetUserEntityById(JohnId); - response.IsSuccessStatusCode.ShouldBeEquivalentTo(false); - response.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.Forbidden); + response.IsSuccessStatusCode.Should().Be(false); + response.StatusCode.Should().Be(HttpStatusCode.Forbidden); } } } \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/Faileds/Users/Get_invalid_user_info_using_nodes.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/Faileds/Users/Get_invalid_user_info_using_nodes.cs index 83648d3..42df8c1 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/Faileds/Users/Get_invalid_user_info_using_nodes.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/Faileds/Users/Get_invalid_user_info_using_nodes.cs @@ -20,8 +20,8 @@ public UnauthorizedGetUserInfoUsingNodes(OsdrWebTestHarness fixture, ITestOutput public async Task WebApi_GetUserInfoUsingNodesEndpoint_ReturnsError() { var response = await UnauthorizedApi.GetNodeById(JohnId); - response.IsSuccessStatusCode.ShouldBeEquivalentTo(false); - response.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.Forbidden); + response.IsSuccessStatusCode.Should().Be(false); + response.StatusCode.Should().Be(HttpStatusCode.Forbidden); } } } \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/Folders/CreateNewFolder.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/Folders/CreateNewFolder.cs index 3391c08..a4b90de 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/Folders/CreateNewFolder.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/Folders/CreateNewFolder.cs @@ -51,8 +51,8 @@ public async Task FolderOperations_CreateNewFolder_ExpectedCreatedFolder() public async Task FolderOperations_GetFolderWithUser2_ExpectedNotFound() { var response = await JaneApi.GetFolder(_folderId); - response.IsSuccessStatusCode.ShouldBeEquivalentTo(false); - response.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.Forbidden); + response.IsSuccessStatusCode.Should().Be(false); + response.StatusCode.Should().Be(HttpStatusCode.Forbidden); } } } \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/Folders/DeleteFolder.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/Folders/DeleteFolder.cs index ab47fe1..428ba30 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/Folders/DeleteFolder.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/Folders/DeleteFolder.cs @@ -36,10 +36,8 @@ public async Task FolderOperation_DeleteFolder_FolderIsDeleted() Harness.WaitWhileFolderDeleted(_folderId); var notFoundResponse = await JohnApi.GetFolder(_folderId); - notFoundResponse.IsSuccessStatusCode.ShouldBeEquivalentTo(false); - //Should not return Forbid status code - response.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.Forbidden); -// responseBeNotFoundFolder.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.NotFound); + notFoundResponse.IsSuccessStatusCode.Should().Be(false); + response.StatusCode.Should().Be(HttpStatusCode.Forbidden); } } } \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/Folders/GetFolder.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/Folders/GetFolder.cs index 78c7dc7..c7f7411 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/Folders/GetFolder.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/Folders/GetFolder.cs @@ -22,7 +22,7 @@ public async Task FolderOperation_GetAccessToNotExistingFolder_ReturnsNotFound() var response = await JohnApi.GetFolder(Guid.NewGuid()); response.EnsureSuccessStatusCode(); //Should not return Forbid status code - response.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.NotFound); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); } } } \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/GenericFiles/ImagePngProcessing.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/GenericFiles/ImagePngProcessing.cs index de1cee3..60c1729 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/GenericFiles/ImagePngProcessing.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/GenericFiles/ImagePngProcessing.cs @@ -12,7 +12,7 @@ using Xunit; using Xunit.Abstractions; -namespace Sds.Osdr.WebApi.IntegrationTests +namespace Sds.Osdr.WebApi.IntegrationTests.GenericFiles { public class UploadPngFixture { diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/GenericFiles/MoveFileTests.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/GenericFiles/MoveFileTests.cs new file mode 100644 index 0000000..bf473d5 --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/GenericFiles/MoveFileTests.cs @@ -0,0 +1,124 @@ +using FluentAssertions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.Generic.Domain; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using Sds.Osdr.WebApi.IntegrationTests.Extensions; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests.GenericFiles +{ + public class MoveFileFixture + { + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + public Guid FolderId { get; set; } + + public MoveFileFixture(OsdrWebTestHarness harness) + { + BlobId = harness.JohnBlobStorageClient.AddResource(harness.JohnId.ToString(), "Chemical-diagram.png", new Dictionary() { { "parentId", harness.JohnId } }).Result; + + FileId = harness.WaitWhileFileProcessed(BlobId); + + var folderResponse = harness.JohnApi.CreateFolderEntity(harness.JohnId, "test1").Result; + var folderLocation = folderResponse.Headers.Location.ToString(); + FolderId = Guid.Parse(folderLocation.Substring(folderLocation.LastIndexOf("/") + 1)); + var file = harness.Session.Get(FileId).Result; + var response = harness.JohnApi.SetParentFolder(FileId, file.Version, FolderId).Result; + harness.WaitWhileFileMoved(FileId); + } + } + + [Collection("OSDR Test Harness")] + public class MoveFileTests : OsdrWebTest, IClassFixture + { + private Guid BlobId { get; set; } + private Guid FileId { get; set; } + public Guid FolderId { get; set; } + + public MoveFileTests(OsdrWebTestHarness fixture, ITestOutputHelper output, MoveFileFixture initFixture) : base(fixture, output) + { + BlobId = initFixture.BlobId; + FileId = initFixture.FileId; + FolderId = initFixture.FolderId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Generic)] + public async Task PngUpload_ValidPng_GenerateExpectedFileEntity() + { + var blobInfo = await BlobStorage.GetFileInfo(BlobId, JohnId.ToString()); + blobInfo.Should().NotBeNull(); + + var fileEntityResponse = await JohnApi.GetFileEntityById(FileId); + var fileEntity = JsonConvert.DeserializeObject(await fileEntityResponse.Content.ReadAsStringAsync()); + fileEntity.Should().NotBeNull(); + + fileEntity.Should().ContainsJson($@" + {{ + 'id': '{FileId}', + 'blob': {{ + 'id': '{blobInfo.Id}', + 'bucket': '{JohnId}', + 'length': {blobInfo.Length}, + 'md5': '{blobInfo.MD5}' + }}, + 'subType': '{FileType.Image}', + 'ownedBy': '{JohnId}', + 'createdBy': '{JohnId}', + 'createdDateTime': '{DateTime.UtcNow}', + 'updatedBy': '{JohnId}', + 'updatedDateTime': '{DateTime.UtcNow}', + 'parentId': '{FolderId}', + 'name': '{blobInfo.FileName}', + 'status': '{FileStatus.Processed}', + 'version': *EXIST* + }}"); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Generic)] + public async Task PngUpload_ValidPng_GenerateExpectedFileNode() + { + var blobInfo = await BlobStorage.GetFileInfo(BlobId, JohnId.ToString()); + blobInfo.Should().NotBeNull(); + + var fileNodeResponse = await JohnApi.GetNodeById(FileId); + var fileNode = JsonConvert.DeserializeObject(await fileNodeResponse.Content.ReadAsStringAsync()); + fileNode.Should().ContainsJson($@" + {{ + 'id': '{FileId}', + 'type': 'File', + 'subType': 'Image', + 'blob': {{ + 'id': '{blobInfo.Id}', + 'bucket': '{JohnId}', + 'length': {blobInfo.Length}, + 'md5': '{blobInfo.MD5}' + }}, + 'status': '{FileStatus.Processed}', + 'ownedBy':'{JohnId}', + 'createdBy': '{JohnId}', + 'createdDateTime': '{DateTime.UtcNow}', + 'updatedBy': '{JohnId}', + 'updatedDateTime': '{DateTime.UtcNow}', + 'name': '{blobInfo.FileName}', + 'parentId': '{FolderId}', + 'version': *EXIST* + }}"); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Generic)] + public async Task PngUpload_ValidPng_GenerateExpectedRecordNodesOnlyEmpty() + { + var recordResponse = await JohnApi.GetNodesById(FileId); + var recordNodes = JsonConvert.DeserializeObject(await recordResponse.Content.ReadAsStringAsync()); + + recordNodes.Should().HaveCount(0); + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/GenericFiles/UpdateFileNameTests.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/GenericFiles/UpdateFileNameTests.cs new file mode 100644 index 0000000..3bd997c --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/GenericFiles/UpdateFileNameTests.cs @@ -0,0 +1,118 @@ +using FluentAssertions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.Generic.Domain; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using Sds.Osdr.WebApi.IntegrationTests.Extensions; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests.GenericFiles +{ + public class UpdateFileNameFixture + { + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + + public UpdateFileNameFixture(OsdrWebTestHarness harness) + { + BlobId = harness.JohnBlobStorageClient.AddResource(harness.JohnId.ToString(), "Chemical-diagram.png", new Dictionary() { { "parentId", harness.JohnId } }).Result; + + FileId = harness.WaitWhileFileProcessed(BlobId); + + var file = harness.Session.Get(FileId).Result; + var response = harness.JohnApi.SetFileName(FileId, file.Version, FileId.ToString()).Result; + harness.WaitWhileFileRenamed(FileId); + } + } + + [Collection("OSDR Test Harness")] + public class UpdateFileNameTests : OsdrWebTest, IClassFixture + { + private Guid BlobId { get; set; } + private Guid FileId { get; set; } + + public UpdateFileNameTests(OsdrWebTestHarness fixture, ITestOutputHelper output, UpdateFileNameFixture initFixture) : base(fixture, output) + { + BlobId = initFixture.BlobId; + FileId = initFixture.FileId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Generic)] + public async Task PngUpload_ValidPng_GenerateExpectedFileEntity() + { + var blobInfo = await BlobStorage.GetFileInfo(BlobId, JohnId.ToString()); + blobInfo.Should().NotBeNull(); + + var fileEntityResponse = await JohnApi.GetFileEntityById(FileId); + var fileEntity = JsonConvert.DeserializeObject(await fileEntityResponse.Content.ReadAsStringAsync()); + fileEntity.Should().NotBeNull(); + + fileEntity.Should().ContainsJson($@" + {{ + 'id': '{FileId}', + 'blob': {{ + 'id': '{blobInfo.Id}', + 'bucket': '{JohnId}', + 'length': {blobInfo.Length}, + 'md5': '{blobInfo.MD5}' + }}, + 'subType': '{FileType.Image}', + 'ownedBy': '{JohnId}', + 'createdBy': '{JohnId}', + 'createdDateTime': '{DateTime.UtcNow}', + 'updatedBy': '{JohnId}', + 'updatedDateTime': '{DateTime.UtcNow}', + 'parentId': '{JohnId}', + 'name': '{FileId.ToString()}', + 'status': '{FileStatus.Processed}', + 'version': *EXIST* + }}"); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Generic)] + public async Task PngUpload_ValidPng_GenerateExpectedFileNode() + { + var blobInfo = await BlobStorage.GetFileInfo(BlobId, JohnId.ToString()); + blobInfo.Should().NotBeNull(); + + var fileNodeResponse = await JohnApi.GetNodeById(FileId); + var fileNode = JsonConvert.DeserializeObject(await fileNodeResponse.Content.ReadAsStringAsync()); + fileNode.Should().ContainsJson($@" + {{ + 'id': '{FileId}', + 'type': 'File', + 'subType': 'Image', + 'blob': {{ + 'id': '{blobInfo.Id}', + 'bucket': '{JohnId}', + 'length': {blobInfo.Length}, + 'md5': '{blobInfo.MD5}' + }}, + 'status': '{FileStatus.Processed}', + 'ownedBy':'{JohnId}', + 'createdBy': '{JohnId}', + 'createdDateTime': '{DateTime.UtcNow}', + 'updatedBy': '{JohnId}', + 'updatedDateTime': '{DateTime.UtcNow}', + 'name': '{FileId.ToString()}', + 'parentId': '{JohnId}', + 'version': *EXIST* + }}"); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Generic)] + public async Task PngUpload_ValidPng_GenerateExpectedRecordNodesOnlyEmpty() + { + var recordResponse = await JohnApi.GetNodesById(FileId); + var recordNodes = JsonConvert.DeserializeObject(await recordResponse.Content.ReadAsStringAsync()); + + recordNodes.Should().HaveCount(0); + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/GenericFiles/When_update_metadata.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/GenericFiles/When_update_metadata.cs new file mode 100644 index 0000000..c4effc3 --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/GenericFiles/When_update_metadata.cs @@ -0,0 +1,67 @@ +using FluentAssertions; +using Newtonsoft.Json.Linq; +using Sds.Osdr.Generic.Domain; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using Sds.Osdr.WebApi.IntegrationTests.Extensions; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests.GenericFiles +{ + [Collection("OSDR Test Harness")] + public class UpdateMetadata : OsdrWebTest + { + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + + public UpdateMetadata(OsdrWebTestHarness fixture, ITestOutputHelper output) : base(fixture, output) + { + BlobId = fixture.JohnBlobStorageClient.AddResource(fixture.JohnId.ToString(), "2018-02-14.gif", new Dictionary() { { "parentId", fixture.JohnId } }).Result; + + FileId = fixture.WaitWhileFileProcessed(BlobId); + } + + [Fact, WebApiTrait(TraitGroup.All)] + public async Task UpdateMetadata_UpdateGenericMetadata_ExpectedRenamedFolder() + { + var processedFile = await Harness.Session.Get(FileId); + + var url = $"/api/entities/files/{FileId}?version={processedFile.Version}"; + + var data = $"[{{'op':'replace','path':'Metadata','value':[{{'name':'test1', 'value': 'value1'}}]}}]"; + + JohnApi.PatchData(url, data).Wait(); + + Harness.WaitMetadataUpdated(FileId); + + var response = await JohnApi.GetFileEntityById(FileId); + response.EnsureSuccessStatusCode(); + var json = JToken.Parse(await response.Content.ReadAsStringAsync()); + + json.Should().ContainsJson($@" + {{ + 'id': '{FileId}', + 'createdBy': '{JohnId}', + 'createdDateTime': *EXIST*, + 'updatedBy': '{JohnId}', + 'updatedDateTime': *EXIST*, + 'ownedBy': '{JohnId}', + 'version': 8, + 'properties': {{ + 'metadata': + [ + {{ + 'name': 'test1', + 'value': 'value1' + }} + ] + }} + }}"); + } + } +} diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/MachineLearning/TrainOneValidModel.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/MachineLearning/TrainOneValidModel.cs index 5344223..8343a58 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/MachineLearning/TrainOneValidModel.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/MachineLearning/TrainOneValidModel.cs @@ -219,7 +219,7 @@ public async Task MlProcessing_ModelTraining_ModelProcessed() modelResponse.EnsureSuccessStatusCode(); var modelJson = JToken.Parse(await modelResponse.Content.ReadAsStringAsync()); - modelJson["status"].ToObject().ShouldBeEquivalentTo("Processed"); + modelJson["status"].ToObject().Should().Be("Processed"); } [Fact, WebApiTrait(TraitGroup.All, TraitGroup.MachineLearning)] diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/MachineLearning/TrainOneValidModelWithDelays.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/MachineLearning/TrainOneValidModelWithDelays.cs index cf5c20f..4191626 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/MachineLearning/TrainOneValidModelWithDelays.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/MachineLearning/TrainOneValidModelWithDelays.cs @@ -30,7 +30,7 @@ public TrainOneValidModelWithDelays(OsdrWebTestHarness fixture, ITestOutputHelpe FolderId = initFixture.FolderId; } - [Fact, WebApiTrait(TraitGroup.All, TraitGroup.MachineLearning)] + [Fact(Skip = "Unstable"), WebApiTrait(TraitGroup.All, TraitGroup.MachineLearning)] public async Task MlProcessing_ModelTrainingWithDelays_ThereAreNoErrors() { Harness.GetFaults().Should().BeEmpty(); diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/MachineLearning/TrainOneValidModelWithSuccessOptimization.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/MachineLearning/TrainOneValidModelWithSuccessOptimization.cs index 42cd9d3..5fe2244 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/MachineLearning/TrainOneValidModelWithSuccessOptimization.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/MachineLearning/TrainOneValidModelWithSuccessOptimization.cs @@ -219,7 +219,7 @@ public async Task MlProcessing_ModelTraining_ModelProcessed() modelResponse.EnsureSuccessStatusCode(); var modelJson = JToken.Parse(await modelResponse.Content.ReadAsStringAsync()); - modelJson["status"].ToObject().ShouldBeEquivalentTo("Processed"); + modelJson["status"].ToObject().Should().Be("Processed"); } [Fact(Skip ="Unstable"), WebApiTrait(TraitGroup.All, TraitGroup.MachineLearning)] diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/Microscopy/When_processing_valid_microscopy.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/Microscopy/When_processing_valid_microscopy.cs new file mode 100644 index 0000000..5aff0f3 --- /dev/null +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/Microscopy/When_processing_valid_microscopy.cs @@ -0,0 +1,117 @@ +using FluentAssertions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sds.Osdr.Generic.Domain; +using Sds.Osdr.IntegrationTests; +using Sds.Osdr.IntegrationTests.FluentAssersions; +using Sds.Osdr.IntegrationTests.Traits; +using Sds.Osdr.WebApi.IntegrationTests.Extensions; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Sds.Osdr.WebApi.IntegrationTests +{ + public class UploadValidMicroscopyFixture + { + public Guid BlobId { get; set; } + public Guid FileId { get; set; } + + public UploadValidMicroscopyFixture(OsdrWebTestHarness harness) + { + BlobId = harness.JohnBlobStorageClient.AddResource(harness.JohnId.ToString(), "Nikon_BF007.nd2", new Dictionary() { { "parentId", harness.JohnId } }).Result; + + FileId = harness.WaitWhileFileProcessed(BlobId); + } + } + + [Collection("OSDR Test Harness")] + public class UploadValidMicroscopy : OsdrWebTest, IClassFixture + { + private Guid BlobId { get; set; } + private Guid FileId { get; set; } + + public UploadValidMicroscopy(OsdrWebTestHarness fixture, ITestOutputHelper output, UploadValidMicroscopyFixture initFixture) : base(fixture, output) + { + BlobId = initFixture.BlobId; + FileId = initFixture.FileId; + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Microscopy)] + public async Task MicroscopyUpload_ValidMicroscopy_GenerateExpectedFileEntity() + { + var blobInfo = await BlobStorage.GetFileInfo(BlobId, JohnId.ToString()); + blobInfo.Should().NotBeNull(); + + var fileEntityResponse = await JohnApi.GetFileEntityById(FileId); + var fileEntity = JsonConvert.DeserializeObject(await fileEntityResponse.Content.ReadAsStringAsync()); + fileEntity.Should().NotBeNull(); + + fileEntity.Should().ContainsJson($@" + {{ + 'id': '{FileId}', + 'blob': {{ + 'id': '{blobInfo.Id}', + 'bucket': '{JohnId}', + 'length': {blobInfo.Length}, + 'md5': '{blobInfo.MD5}' + }}, + 'subType': '{FileType.Microscopy}', + 'ownedBy': '{JohnId}', + 'createdBy': '{JohnId}', + 'createdDateTime': '{DateTime.UtcNow}', + 'updatedBy': '{JohnId}', + 'updatedDateTime': '{DateTime.UtcNow}', + 'parentId': '{JohnId}', + 'name': '{blobInfo.FileName}', + 'status': '{FileStatus.Processed}', + 'version': *EXIST* + }}"); + fileEntity["images"].Should().NotBeNull(); + fileEntity["images"].Should().HaveCount(3); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Microscopy)] + public async Task MicroscopyUpload_ValidMicroscopy_GenerateExpectedFileNode() + { + var blobInfo = await BlobStorage.GetFileInfo(BlobId, JohnId.ToString()); + blobInfo.Should().NotBeNull(); + + var fileNodeResponse = await JohnApi.GetNodeById(FileId); + var fileNode = JsonConvert.DeserializeObject(await fileNodeResponse.Content.ReadAsStringAsync()); + fileNode.Should().ContainsJson($@" + {{ + 'id': '{FileId}', + 'type': 'File', + 'subType': '{FileType.Microscopy}', + 'blob': {{ + 'id': '{blobInfo.Id}', + 'bucket': '{JohnId}', + 'length': {blobInfo.Length}, + 'md5': '{blobInfo.MD5}' + }}, + 'status': '{FileStatus.Processed}', + 'ownedBy':'{JohnId}', + 'createdBy': '{JohnId}', + 'createdDateTime': '{DateTime.UtcNow}', + 'updatedBy': '{JohnId}', + 'updatedDateTime': '{DateTime.UtcNow}', + 'name': '{blobInfo.FileName}', + 'parentId': '{JohnId}', + 'version': *EXIST* + }}"); + fileNode["images"].Should().NotBeNull(); + fileNode["images"].Should().HaveCount(3); + } + + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Microscopy)] + public async Task MicroscopyUpload_ValidMicroscopy_GenerateExpectedRecordNodeOnlyEmpty() + { + var recordResponse = await JohnApi.GetNodesById(FileId); + var recordNodes = JsonConvert.DeserializeObject(await recordResponse.Content.ReadAsStringAsync()); + recordNodes.Should().HaveCount(0); + } + } +} \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/CreatePublicLinkForEntityImage.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/CreatePublicLinkForEntityImage.cs index 54180ea..df1afd5 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/CreatePublicLinkForEntityImage.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/CreatePublicLinkForEntityImage.cs @@ -51,10 +51,10 @@ public async Task FileSharing_WithAuthorizeUser_ReturnsExpectedImage() var blobResponse = await JohnApi.GetImagesFileEntityById(FileId, imageId); blobResponse.EnsureSuccessStatusCode(); - blobResponse.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.OK); - blobResponse.Content.Headers.ContentType.MediaType.ShouldBeEquivalentTo("application/octet-stream"); + blobResponse.StatusCode.Should().Be(HttpStatusCode.OK); + blobResponse.Content.Headers.ContentType.MediaType.Should().Be("application/octet-stream"); blobResponse.Content.Headers.ContentLength.Should().BeGreaterThan(10000); - blobResponse.Content.Headers.ContentDisposition.FileName.ShouldBeEquivalentTo("Aspirin.mol.svg"); + blobResponse.Content.Headers.ContentDisposition.FileName.Should().Be("Aspirin.mol.svg"); } [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Sharing)] @@ -66,10 +66,10 @@ public async Task FileSharing_WithUnauthorizeUser_ReturnsExpectedImage() var blobResponse = await UnauthorizedApi.GetImagesFileEntityById(FileId, imageId); blobResponse.EnsureSuccessStatusCode(); - blobResponse.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.OK); - blobResponse.Content.Headers.ContentType.MediaType.ShouldBeEquivalentTo("application/octet-stream"); + blobResponse.StatusCode.Should().Be(HttpStatusCode.OK); + blobResponse.Content.Headers.ContentType.MediaType.Should().Be("application/octet-stream"); blobResponse.Content.Headers.ContentLength.Should().BeGreaterThan(10000); - blobResponse.Content.Headers.ContentDisposition.FileName.ShouldBeEquivalentTo("Aspirin.mol.svg"); + blobResponse.Content.Headers.ContentDisposition.FileName.Should().Be("Aspirin.mol.svg"); } } } \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/CreatePublicLinkForFile.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/CreatePublicLinkForFile.cs index 8e53942..1615004 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/CreatePublicLinkForFile.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/CreatePublicLinkForFile.cs @@ -48,8 +48,8 @@ public async Task FileSharing_WithAuthorizeUser_ReturnsExpectedBlobFile() { var blobResponse = await JohnApi.GetBlobFileEntityById(FileId, BlobId); blobResponse.EnsureSuccessStatusCode(); - blobResponse.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.OK); - blobResponse.Content.Headers.ContentType.MediaType.ShouldBeEquivalentTo("application/octet-stream"); + blobResponse.StatusCode.Should().Be(HttpStatusCode.OK); + blobResponse.Content.Headers.ContentType.MediaType.Should().Be("application/octet-stream"); blobResponse.Content.Headers.ContentLength.Should().BeGreaterThan(1500); //blobResponse.Content.Headers.ContentDisposition.FileName.ShouldBeEquivalentTo("Aspirin.mol"); } @@ -59,8 +59,8 @@ public async Task FileSharing_WithAuthorizeUser2_ReturnsExpectedBlobFile() { var blobResponse = await JaneApi.GetBlobFileEntityById(FileId, BlobId); blobResponse.EnsureSuccessStatusCode(); - blobResponse.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.OK); - blobResponse.Content.Headers.ContentType.MediaType.ShouldBeEquivalentTo("application/octet-stream"); + blobResponse.StatusCode.Should().Be(HttpStatusCode.OK); + blobResponse.Content.Headers.ContentType.MediaType.Should().Be("application/octet-stream"); blobResponse.Content.Headers.ContentLength.Should().BeGreaterThan(1500); //blobResponse.Content.Headers.ContentDisposition.FileName.ShouldBeEquivalentTo("Aspirin.mol"); } @@ -89,8 +89,8 @@ public async Task FileSharing_WithUnauthorizeUser_ReturnsExpectedBlobFile() { var blobResponse = await UnauthorizedApi.GetBlobFileEntityById(FileId, BlobId); blobResponse.EnsureSuccessStatusCode(); - blobResponse.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.OK); - blobResponse.Content.Headers.ContentType.MediaType.ShouldBeEquivalentTo("application/octet-stream"); + blobResponse.StatusCode.Should().Be(HttpStatusCode.OK); + blobResponse.Content.Headers.ContentType.MediaType.Should().Be("application/octet-stream"); blobResponse.Content.Headers.ContentLength.Should().BeGreaterThan(1500); //blobResponse.Content.Headers.ContentDisposition.FileName.ShouldBeEquivalentTo("Aspirin.mol"); } @@ -99,9 +99,9 @@ public async Task FileSharing_WithUnauthorizeUser_ReturnsExpectedBlobFile() public async Task FileSharing_WithAuthorizeUser_ReturnsRecordNotFound() { var blobResponse = await JohnApi.GetBlobRecordEntityById(FileId, BlobId); - blobResponse.IsSuccessStatusCode.ShouldBeEquivalentTo(false); - blobResponse.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.NotFound); - blobResponse.ReasonPhrase.ShouldAllBeEquivalentTo("Not Found"); + blobResponse.IsSuccessStatusCode.Should().Be(false); + blobResponse.StatusCode.Should().Be(HttpStatusCode.NotFound); + blobResponse.ReasonPhrase.Should().Be("Not Found"); } } } \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/CreatePublicLinkForRecord.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/CreatePublicLinkForRecord.cs index 3156f9a..32dff91 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/CreatePublicLinkForRecord.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/CreatePublicLinkForRecord.cs @@ -57,8 +57,8 @@ public async Task FileSharing_WithUnauthorizeUser_ReturnsExpectedBlobRecord() var blobResponse = await UnauthorizedApi.GetBlobRecordEntityById(recordId, recordBlobId); blobResponse.EnsureSuccessStatusCode(); - blobResponse.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.OK); - blobResponse.Content.Headers.ContentType.MediaType.ShouldBeEquivalentTo("chemical/x-mdl-molfile"); + blobResponse.StatusCode.Should().Be(HttpStatusCode.OK); + blobResponse.Content.Headers.ContentType.MediaType.Should().Be("chemical/x-mdl-molfile"); blobResponse.Content.Headers.ContentLength.Should().BeGreaterThan(1500); //blobResponse.Content.Headers.ContentDisposition.FileName.Should().NotBeNullOrEmpty(); } @@ -77,8 +77,8 @@ public async Task FileSharin_WithAuthorizeUser_ReturnsExpectedBlobRecord() var blobResponse = await JohnApi.GetBlobRecordEntityById(recordId, recordBlobId); blobResponse.EnsureSuccessStatusCode(); - blobResponse.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.OK); - blobResponse.Content.Headers.ContentType.MediaType.ShouldBeEquivalentTo("chemical/x-mdl-molfile"); + blobResponse.StatusCode.Should().Be(HttpStatusCode.OK); + blobResponse.Content.Headers.ContentType.MediaType.Should().Be("chemical/x-mdl-molfile"); blobResponse.Content.Headers.ContentLength.Should().BeGreaterThan(1500); //blobResponse.Content.Headers.ContentDisposition.FileName.Should().NotBeNullOrEmpty(); } diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/CreatePublicLinkForRecordImage.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/CreatePublicLinkForRecordImage.cs index a36a8dd..f191321 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/CreatePublicLinkForRecordImage.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/CreatePublicLinkForRecordImage.cs @@ -57,10 +57,10 @@ public async Task FileSharing_WithUnauthorizeUser_ReturnsExpectedImageRecord() var blobResponse = await UnauthorizedApi.GetImagesRecordEntityById(recordId, imageId); blobResponse.EnsureSuccessStatusCode(); - blobResponse.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.OK); - blobResponse.Content.Headers.ContentType.MediaType.ShouldBeEquivalentTo("application/octet-stream"); + blobResponse.StatusCode.Should().Be(HttpStatusCode.OK); + blobResponse.Content.Headers.ContentType.MediaType.Should().Be("application/octet-stream"); blobResponse.Content.Headers.ContentLength.Should().BeGreaterThan(10000); - blobResponse.Content.Headers.ContentDisposition.FileName.ShouldBeEquivalentTo("Aspirin.mol.svg"); + blobResponse.Content.Headers.ContentDisposition.FileName.Should().Be("Aspirin.mol.svg"); } [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Sharing)] @@ -77,10 +77,10 @@ public async Task FileSharing_WithAuthorizeUser_ReturnsExpectedImageRecord() var blobResponse = await JohnApi.GetImagesRecordEntityById(recordId, imageId); blobResponse.EnsureSuccessStatusCode(); - blobResponse.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.OK); - blobResponse.Content.Headers.ContentType.MediaType.ShouldBeEquivalentTo("application/octet-stream"); + blobResponse.StatusCode.Should().Be(HttpStatusCode.OK); + blobResponse.Content.Headers.ContentType.MediaType.Should().Be("application/octet-stream"); blobResponse.Content.Headers.ContentLength.Should().BeGreaterThan(10000); - blobResponse.Content.Headers.ContentDisposition.FileName.ShouldBeEquivalentTo("Aspirin.mol.svg"); + blobResponse.Content.Headers.ContentDisposition.FileName.Should().Be("Aspirin.mol.svg"); } } } diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/FileCreatePublicSharing.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/FileCreatePublicSharing.cs index a72ba64..e04e056 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/FileCreatePublicSharing.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/FileCreatePublicSharing.cs @@ -54,7 +54,7 @@ public async Task FileSharing_WithUnauthorizeUser_ReturnsExpectedFileEntity() var fileEntityResponse = await UnauthorizedApi.GetFileEntityById(FileId); fileEntityResponse.EnsureSuccessStatusCode(); - fileEntityResponse.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.OK); + fileEntityResponse.StatusCode.Should().Be(HttpStatusCode.OK); var fileEntity = JsonConvert.DeserializeObject(await fileEntityResponse.Content.ReadAsStringAsync()); fileEntity.Should().ContainsJson($@" @@ -91,7 +91,7 @@ public async Task FileSharing_WithUnauthorizeUser_ReturnsExpectedFileNode() var fileEntityResponse = await UnauthorizedApi.GetNodeById(FileId); fileEntityResponse.EnsureSuccessStatusCode(); - fileEntityResponse.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.OK); + fileEntityResponse.StatusCode.Should().Be(HttpStatusCode.OK); var fileEntity = JsonConvert.DeserializeObject(await fileEntityResponse.Content.ReadAsStringAsync()); fileEntity.Should().ContainsJson($@" @@ -254,18 +254,18 @@ public async Task FileSharing_WithUnauthorizeUser_ReturnsExpectedNotFound() blobInfo.Should().NotBeNull(); var response = await UnauthorizedApi.GetNodeEntityById(FileId); - response.IsSuccessStatusCode.ShouldBeEquivalentTo(false); - response.StatusCode.ShouldBeEquivalentTo(400); - response.ReasonPhrase.ShouldAllBeEquivalentTo("Bad Request"); + response.IsSuccessStatusCode.Should().Be(false); + response.StatusCode.Should().Be(400); + response.ReasonPhrase.Should().Be("Bad Request"); } [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Sharing)] public async Task FileSharing_WithUnauthorizeUser_ReturnsRecordNotFound() { var response = await UnauthorizedApi.GetRecordEntityById(FileId); - response.IsSuccessStatusCode.ShouldBeEquivalentTo(false); - response.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.NotFound); - response.ReasonPhrase.ShouldAllBeEquivalentTo("Not Found"); + response.IsSuccessStatusCode.Should().Be(false); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + response.ReasonPhrase.Should().Be("Not Found"); } } } \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/FileDeletePublicSharing.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/FileDeletePublicSharing.cs index d59905d..6e8244d 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/FileDeletePublicSharing.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/FileDeletePublicSharing.cs @@ -47,8 +47,8 @@ public FileDeletePublicSharing(OsdrWebTestHarness fixture, ITestOutputHelper out public async Task FileSharing_WithUnauthorizeUser_ReturnsExpectedNotFoundFileEntity() { var response = await UnauthorizedApi.GetFileEntityById(FileId); - response.IsSuccessStatusCode.ShouldBeEquivalentTo(false); - response.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.NotFound); + response.IsSuccessStatusCode.Should().Be(false); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); //response.ReasonPhrase.ShouldAllBeEquivalentTo("Not Found"); } @@ -57,7 +57,7 @@ public async Task FileSharing_WithUnauthorizeUser_ReturnsExpectedNotFoundFileNod { var response = await UnauthorizedApi.GetNodeById(FileId); var sharedInfo = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - response.IsSuccessStatusCode.ShouldBeEquivalentTo(false); + response.IsSuccessStatusCode.Should().Be(false); // response.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.NotFound); // response.ReasonPhrase.ShouldAllBeEquivalentTo("Not Found"); } @@ -82,7 +82,7 @@ public async Task FileSharing_ListOfSharedFiles_ContainsExpectedNotFoundFileNode public async Task FileSharing_WithUnauthorizeUser_ReturnsExpectedNotFoundRecordEntity() { var response = await UnauthorizedApi.GetNodesById(FileId); - response.IsSuccessStatusCode.ShouldBeEquivalentTo(false); + response.IsSuccessStatusCode.Should().Be(false); // response.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.NotFound); // response.ReasonPhrase.ShouldAllBeEquivalentTo("Not Found"); } @@ -97,7 +97,7 @@ public async Task FileSharing_WithUnauthorizeUser_ReturnsExpectedNotFoundRecordN recordId.Should().NotBeEmpty(); var response = await UnauthorizedApi.GetNodeById(recordId); - response.IsSuccessStatusCode.ShouldBeEquivalentTo(false); + response.IsSuccessStatusCode.Should().Be(false); // response.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.NotFound); // response.ReasonPhrase.ShouldAllBeEquivalentTo("Not Found"); } @@ -106,7 +106,7 @@ public async Task FileSharing_WithUnauthorizeUser_ReturnsExpectedNotFoundRecordN public async Task BlobRecordSharing_WithUnauthorizeUser_ReturnsExpectedNotFound() { var response = await UnauthorizedApi.GetBlobRecordEntityById(FileId, BlobId); - response.IsSuccessStatusCode.ShouldBeEquivalentTo(false); + response.IsSuccessStatusCode.Should().Be(false); // response.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.NotFound); // response.ReasonPhrase.ShouldAllBeEquivalentTo("Not Found"); } @@ -115,7 +115,7 @@ public async Task BlobRecordSharing_WithUnauthorizeUser_ReturnsExpectedNotFound( public async Task BlobFileSharing_WithUnauthorizeUser_ReturnsExpectedNotFound() { var response = await UnauthorizedApi.GetBlobFileEntityById(FileId, BlobId); - response.IsSuccessStatusCode.ShouldBeEquivalentTo(false); + response.IsSuccessStatusCode.Should().Be(false); // response.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.NotFound); // response.ReasonPhrase.ShouldAllBeEquivalentTo("Not Found"); } @@ -128,7 +128,7 @@ public async Task BlobImageSharing_WithUnauthorizeUser_ReturnsExpectedNotFound() var imageId = file["images"].First()["id"].ToObject(); var response = await UnauthorizedApi.GetImagesFileEntityById(FileId, imageId); - response.IsSuccessStatusCode.ShouldBeEquivalentTo(false); + response.IsSuccessStatusCode.Should().Be(false); // response.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.NotFound); // response.ReasonPhrase.ShouldAllBeEquivalentTo("Not Found"); } @@ -146,7 +146,7 @@ public async Task BlobImageRecordSharing_WithUnauthorizeUser_ReturnsExpectedNotF var imageId = record["images"].First()["id"].ToObject(); var response = await UnauthorizedApi.GetImagesRecordEntityById(recordId, imageId); - response.IsSuccessStatusCode.ShouldBeEquivalentTo(false); + response.IsSuccessStatusCode.Should().Be(false); // response.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.NotFound); // response.ReasonPhrase.ShouldAllBeEquivalentTo("Not Found"); } diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/SharingPngFile.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/SharingPngFile.cs index 8180a86..064a4d8 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/SharingPngFile.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/Sharing/SharingPngFile.cs @@ -51,10 +51,10 @@ public async Task FileSharing_WithAuthorizeUser_ReturnsExpectedImage() var blobResponse = await JohnApi.GetImagesFileEntityById(FileId, imageId); blobResponse.EnsureSuccessStatusCode(); - blobResponse.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.OK); - blobResponse.Content.Headers.ContentType.MediaType.ShouldBeEquivalentTo("application/octet-stream"); - blobResponse.Content.Headers.ContentLength.ShouldBeEquivalentTo(175430); - blobResponse.Content.Headers.ContentDisposition.FileName.ShouldBeEquivalentTo("Chemical-diagram.png"); + blobResponse.StatusCode.Should().Be(HttpStatusCode.OK); + blobResponse.Content.Headers.ContentType.MediaType.Should().Be("application/octet-stream"); + blobResponse.Content.Headers.ContentLength.Should().Be(175430); + blobResponse.Content.Headers.ContentDisposition.FileName.Should().Be("Chemical-diagram.png"); } [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Sharing)] @@ -66,10 +66,10 @@ public async Task FileSharing_WithUnauthorizeUser_ReturnsExpectedImage() var blobResponse = await UnauthorizedApi.GetImagesFileEntityById(FileId, imageId); blobResponse.EnsureSuccessStatusCode(); - blobResponse.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.OK); - blobResponse.Content.Headers.ContentType.MediaType.ShouldBeEquivalentTo("application/octet-stream"); - blobResponse.Content.Headers.ContentLength.ShouldBeEquivalentTo(175430); - blobResponse.Content.Headers.ContentDisposition.FileName.ShouldBeEquivalentTo("Chemical-diagram.png"); + blobResponse.StatusCode.Should().Be(HttpStatusCode.OK); + blobResponse.Content.Headers.ContentType.MediaType.Should().Be("application/octet-stream"); + blobResponse.Content.Headers.ContentLength.Should().Be(175430); + blobResponse.Content.Headers.ContentDisposition.FileName.Should().Be("Chemical-diagram.png"); } } } \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/Streams/GetStramAllAggregates.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/Streams/GetStramAllAggregates.cs index 3e1951a..464062d 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/Streams/GetStramAllAggregates.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/Streams/GetStramAllAggregates.cs @@ -70,8 +70,8 @@ public async Task Stream_UseJohn_ReturnsExpectedStreamContent() public async Task Stream_UseJane_ReturnProhibitedAccess() { var response = await JaneApi.GetStreamFileEntityById(FileId, 0, 1); - response.StatusCode.ShouldBeEquivalentTo(HttpStatusCode.Forbidden); - response.ReasonPhrase.ShouldAllBeEquivalentTo("Forbidden"); + response.StatusCode.Should().Be(HttpStatusCode.Forbidden); + response.ReasonPhrase.Should().Be("Forbidden"); } } } \ No newline at end of file diff --git a/Sds.Osdr.WebApi.IntegrationTests/Tests/Substances/ValidMolProcessing.cs b/Sds.Osdr.WebApi.IntegrationTests/Tests/Substances/ValidMolProcessing.cs index 7c95f3f..909e79d 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/Tests/Substances/ValidMolProcessing.cs +++ b/Sds.Osdr.WebApi.IntegrationTests/Tests/Substances/ValidMolProcessing.cs @@ -83,6 +83,7 @@ public async Task ChemicalProcessing_ValidMol_GenerateExpectedFileEntity() }} }}"); } + [Fact, WebApiTrait(TraitGroup.All, TraitGroup.Chemical)] public async Task ChemicalProcessing_ValidMol_GenerateExpectedFileNode() { diff --git a/Sds.Osdr.WebApi.IntegrationTests/appsettings.json b/Sds.Osdr.WebApi.IntegrationTests/appsettings.json index e2d542e..a54796d 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/appsettings.json +++ b/Sds.Osdr.WebApi.IntegrationTests/appsettings.json @@ -1,10 +1,16 @@ { + "KeyCloak": { + "Authority": "%IDENTITY_SERVER_URL%" + }, "OsdrWebApi": { "Base": "http://localhost:28611" }, "MongoDb": { "ConnectionString": "%OSDR_MONGO_DB%" }, + "ElasticSearch": { + "ConnectionString": "%OSDR_ES%" + }, "GridFs": { "ConnectionString": "%OSDR_GRID_FS%" }, @@ -27,7 +33,7 @@ "TcpPort": 11030 }, "Serilog": { - "MinimumLevel": "Information", + "MinimumLevel": "Debug", "WriteTo": [ { "Name": "RollingFile", @@ -36,6 +42,9 @@ "pathFormat": "%OSDR_LOG_FOLDER%/sds-osdr-integrationtests-{Date}.log", "retainedFileCountLimit": 5 } + }, + { + "Name": "Console" } ] } diff --git a/Sds.Osdr.WebApi.IntegrationTests/docker-compose.yml b/Sds.Osdr.WebApi.IntegrationTests/docker-compose.yml index dc9c02a..efc8db2 100644 --- a/Sds.Osdr.WebApi.IntegrationTests/docker-compose.yml +++ b/Sds.Osdr.WebApi.IntegrationTests/docker-compose.yml @@ -3,39 +3,39 @@ version: '3.4' services: eventstore: image: eventstore/eventstore:release-4.0.2 - # ports: - # - "2113:2113" - # - "1113:1113" + ports: + - "2113:2113" + - "1113:1113" environment: - - RUN_PROJECTIONS = All + - RUN_PROJECTIONS=All networks: - - osdr-test + - leanda-net redis: image: redis:4-alpine command: redis-server --appendonly yes - # ports: - # - "6379:6379" + ports: + - "6379:6379" networks: - - osdr-test + - leanda-net rabbitmq: - image: docker.your-company.com/osdr-rabbitmq:3.6 - hostname: "rabbitmq-test" + image: leanda/rabbitmq + hostname: "leanda" environment: - - RABBITMQ_DEFAULT_VHOST=osdr_test - # ports: - # - "18282:15672" - # - "5672:5672" + - RABBITMQ_DEFAULT_VHOST=leanda + ports: + - "8282:15672" + - "5672:5672" networks: - - osdr-test + - leanda-net mongo: image: mongo:3.6 - # ports: - # - "27017:27017" + ports: + - "27017:27017" networks: - - osdr-test + - leanda-net postgres: image: postgres @@ -46,7 +46,7 @@ services: POSTGRES_ROOT_PASSWORD: keycloak pgdata: data-pstgresql networks: - - osdr-test + - leanda-net keycloak: build: KeyCloak @@ -59,114 +59,124 @@ services: POSTGRES_PORT_5432_TCP_ADDR: postgres POSTGRES_DATABASE: keycloak JDBC_PARAMS: 'connectTimeout=30' - # ports: - # - '8080:8080' + ports: + - '8080:8080' networks: - - osdr-test + - leanda-net depends_on: - postgres - osdr-service-backend: - container_name: osdr-service-backend - image: docker.your-company.com/osdr-service-backend:ci-${BUILD_NUMBER} + elasticsearch: + container_name: elasticsearch + image: docker.elastic.co/elasticsearch/elasticsearch:6.8.4 + environment: + - discovery.type=single-node + ports: + - "9201:9201" + - "9200:9200" + - "9301:9300" + networks: + - leanda-net + + core-backend: + container_name: core-backend + image: leanda/core-backend:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - OSDR_EVENT_STORE=ConnectTo=tcp://admin:changeit@eventstore:1113 - OSDR_REDIS=redis - - OSDR_LOG_LEVEL=Error - command: ./wait-for-it.sh rabbitmq:15672 -t 30 -- ./Sds.Osdr.Domain.BackEnd + - OSDR_LOG_LEVEL=${OSDR_LOG_LEVEL} + command: ./wait-for-it.sh rabbitmq:15672 -t 60 -- ./Sds.Osdr.Domain.BackEnd volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test + - leanda-net depends_on: - rabbitmq - eventstore - redis - mongo - osdr-service-frontend: - container_name: osdr-service-frontend - image: docker.your-company.com/osdr-service-frontend:ci-${BUILD_NUMBER} + core-frontend: + container_name: core-frontend + image: leanda/core-frontend:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - OSDR_EVENT_STORE=ConnectTo=tcp://admin:changeit@eventstore:1113 - OSDR_REDIS=redis - - OSDR_LOG_LEVEL=Error - command: ./wait-for-it.sh rabbitmq:15672 -t 30 -- ./Sds.Osdr.Domain.FrontEnd + - OSDR_LOG_LEVEL=${OSDR_LOG_LEVEL} + command: ./wait-for-it.sh rabbitmq:15672 -t 60 -- ./Sds.Osdr.Domain.FrontEnd volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test + - leanda-net depends_on: - rabbitmq - eventstore - redis - mongo - osdr-service-sagahost: - container_name: osdr-service-sagahost - image: docker.your-company.com/osdr-service-sagahost:ci-${BUILD_NUMBER} + core-sagahost: + container_name: core-sagahost + image: leanda/core-sagahost:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test - - OSDR_LOG_LEVEL=Error - command: ./wait-for-it.sh rabbitmq:15672 -t 30 -- ./Sds.Osdr.Domain.SagaHost + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda + - OSDR_LOG_LEVEL=${OSDR_LOG_LEVEL} + command: ./wait-for-it.sh rabbitmq:15672 -t 60 -- ./Sds.Osdr.Domain.SagaHost volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test + - leanda-net depends_on: - rabbitmq - mongo - osdr-service-persistence: - container_name: osdr-service-persistence - image: docker.your-company.com/osdr-service-persistence:ci-${BUILD_NUMBER} + core-persistence: + container_name: core-persistence + image: leanda/core-persistence:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test - - OSDR_LOG_LEVEL=Error - command: ./wait-for-it.sh rabbitmq:15672 -t 30 -- ./Sds.Osdr.Persistence + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda + - OSDR_LOG_LEVEL=${OSDR_LOG_LEVEL} + command: ./wait-for-it.sh rabbitmq:15672 -t 60 -- ./Sds.Osdr.Persistence volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test + - leanda-net depends_on: - rabbitmq - mongo - osdr-service-web-api: - container_name: osdr-service-web-api - image: docker.your-company.com/osdr-service-web-api:ci-${BUILD_NUMBER} + core-web-api: + container_name: core-web-api + image: leanda/core-web-api:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - IDENTITY_SERVER_URL=http://keycloak:8080/auth/realms/OSDR - OSDR_REDIS=redis - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - OSDR_EVENT_STORE=ConnectTo=tcp://admin:changeit@eventstore:1113 - #- OSDR_ES=http://elasticsearch:9200 + - OSDR_ES=http://elasticsearch:9200 - SWAGGER_BASEPATH=/osdr/v1 - - OSDR_LOG_LEVEL=Error - command: ./wait-for-it.sh rabbitmq:15672 -t 30 -- ./wait-for-it.sh keycloak:8080 -t 30 -- ./Sds.Osdr.WebApi + - OSDR_LOG_LEVEL=${OSDR_LOG_LEVEL} + command: ./wait-for-it.sh rabbitmq:15672 -t 60 -- ./wait-for-it.sh keycloak:8080 -t 60 -- ./Sds.Osdr.WebApi volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test - # ports: - # - "28611:18006" + - leanda-net + ports: + - "28611:18006" depends_on: - keycloak - rabbitmq @@ -175,54 +185,61 @@ services: - mongo blob-storage-api: - container_name: osdr-blob-storage-api - image: docker.your-company.com/blob-storage-webapi:latest + container_name: blob-storage-api + image: leanda/blob-storage-webapi entrypoint: /bin/bash environment: - IDENTITY_SERVER_URL=http://keycloak:8080/auth/realms/OSDR - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - SWAGGER_BASEPATH=/blob/v1 - command: ./wait-for-it.sh rabbitmq:15672 -t 30 -- ./wait-for-it.sh keycloak:8080 -t 30 -- ./Sds.Storage.Blob.WebApi + - OSDR_LOG_LEVEL=${OSDR_LOG_LEVEL} + - MAX_BLOB_SIZE=419430400 + command: ./wait-for-it.sh rabbitmq:15672 -t 60 -- ./wait-for-it.sh keycloak:8080 -t 60 -- ./Sds.Storage.Blob.WebApi volumes: - ${OSDR_LOG_FOLDER}:/logs networks: - - osdr-test - # ports: - # - "18006:18006" + - leanda-net + ports: + - "18006:18006" + depends_on: + - keycloak + - rabbitmq + - eventstore + - redis + - mongo integration: - container_name: osdr-webapi-integration-tests - image: docker.your-company.com/osdr-service-webapi-integration:ci-${BUILD_NUMBER} + container_name: webapi-integration + image: leanda/webapi-integration:${TAG_VERSION-latest} entrypoint: /bin/bash environment: - IDENTITY_SERVER_URL=http://keycloak:8080/auth/realms/OSDR + - OSDR_BLOB_STORAGE_API=http://blob-storage-api:18006/api/blobs/ - OSDR_REDIS=redis + - OSDR_ES=http://elasticsearch:9200 - OSDR_LOG_FOLDER=/logs - - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev - - OSDR_GRID_FS=mongodb://mongo:27017/osdr_dev - - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/osdr_test + - OSDR_MONGO_DB=mongodb://mongo:27017/leanda + - OSDR_GRID_FS=mongodb://mongo:27017/leanda + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda - OSDR_EVENT_STORE=ConnectTo=tcp://admin:changeit@eventstore:1113 - - OSDR_WEB_API=http://osdr-service-web-api:18006 - #command: ./wait-for-it.sh http://osdr-service-web-api:18006 -t 30 -- dotnet vstest ./Sds.Osdr.WebApi.IntegrationTests.dll /logger:console;verbosity="normal" - command: ./wait-for-it.sh http://osdr-service-web-api:18006 -t 30 -- dotnet vstest ./Sds.Osdr.WebApi.IntegrationTests.dll /logger:"trx;LogFileName=webapi-integrationtests-results-${BUILD_NUMBER}.xml" /ResultsDirectory:/results + - OSDR_WEB_API=http://core-web-api:18006 + - OSDR_LOG_LEVEL=${OSDR_LOG_LEVEL} + # command: ./wait-for-it.sh http://core-web-api:18006 -t 60 -- dotnet vstest ./Sds.Osdr.WebApi.IntegrationTests.dll /logger:console /Tests:AddOneCategoryToEntity + command: ./wait-for-it.sh http://core-web-api:18006 -t 60 -- dotnet vstest ./Sds.Osdr.WebApi.IntegrationTests.dll /logger:console;verbosity="normal" + #command: ./wait-for-it.sh http://osdr-service-web-api:18006 -t 60 -- dotnet vstest ./Sds.Osdr.WebApi.IntegrationTests.dll /logger:"trx;LogFileName=webapi-integrationtests-results-${BUILD_NUMBER}.xml" /ResultsDirectory:/results volumes: - ${OSDR_LOG_FOLDER}:/logs - /results:/results networks: - - osdr-test + - leanda-net depends_on: - - osdr-service-backend - - osdr-service-frontend - - osdr-service-sagahost - - osdr-service-persistence - - osdr-service-web-api + - core-backend + - core-frontend + - core-sagahost + - core-persistence + - core-web-api networks: - osdr-test: - -#volumes: -# test-results: -# external: true -# name: test-results \ No newline at end of file + leanda-net: diff --git a/Sds.Osdr.WebApi/Controllers/CategoryEntitiesController.cs b/Sds.Osdr.WebApi/Controllers/CategoryEntitiesController.cs new file mode 100644 index 0000000..21fee15 --- /dev/null +++ b/Sds.Osdr.WebApi/Controllers/CategoryEntitiesController.cs @@ -0,0 +1,187 @@ +using Leanda.Categories.Domain.Commands; +using MassTransit; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using MongoDB.Bson; +using MongoDB.Driver; +using Sds.Osdr.WebApi.Filters; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using CQRSlite.Domain; +using Nest; +using Sds.Osdr.WebApi.Requests; +using Sds.Osdr.WebApi.Responses; +using System.Linq; +using Newtonsoft.Json; +using System.Dynamic; +using Sds.Osdr.WebApi.Extensions; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json.Linq; +using Leanda.Categories.Domain; + +namespace Sds.Osdr.WebApi.Controllers +{ + [Route("api/[controller]")] + //[Authorize] + //[UserInfoRequired] + public class CategoryEntitiesController : MongoDbController, IPaginationController + { + private readonly IBusControl Bus; + private readonly IMongoCollection NodesTreeCollection; + private readonly ISession Session; + private readonly IElasticClient ElasticClient; + private readonly IUrlHelper UrlHelper; + + public CategoryEntitiesController(IMongoDatabase database, IBusControl bus, IElasticClient elasticClient, IUrlHelper urlHelper, ISession session) : base(database) + { + Bus = bus ?? throw new ArgumentNullException(nameof(bus)); + UrlHelper = urlHelper ?? throw new ArgumentNullException(nameof(urlHelper)); + NodesTreeCollection = Database.GetCollection("Nodes"); + Session = session ?? throw new ArgumentNullException(nameof(session)); + ElasticClient = elasticClient ?? throw new ArgumentNullException(nameof(elasticClient)); + } + + /// + /// Add categories to entity + /// + /// Entity ID + /// List of categories ID + /// + [HttpPost("entities/{entityId}/categories")] + public async Task AddEntityCategories(Guid entityId, [FromBody] IEnumerable categoriesIds) + { + var node = await NodesTreeCollection.Find(new BsonDocument("_id", entityId)).FirstOrDefaultAsync(); + if (node == null) + return NotFound(); + + await Bus.Publish(new + { + Id = Guid.NewGuid(), + EntityId = entityId, + CategoriesIds = categoriesIds, + UserId + }); + + return Accepted(); + } + + /// + /// Delete categories by categoryId + /// + /// Entity ID + /// Category ID + /// + [HttpDelete("entities/{entityId}/categories/{categoryId}")] + public async Task DeleteEntityCategory(Guid entityId, Guid categoryId) + { + var node = await NodesTreeCollection.Find(new BsonDocument("_id", entityId)).FirstOrDefaultAsync(); + if (node == null) + return NotFound(); + + await Bus.Publish(new + { + Id = Guid.NewGuid(), + EntityId = entityId, + CategoriesIds = new List { categoryId }, + UserId + }); + + return Accepted(); + } + + /// + /// Delete categories by categoriesIds + /// + /// Entity ID + /// List of categories ID + /// + [HttpDelete("entities/{entityId}/categories")] + public async Task DeleteEntityCategories(Guid entityId, [FromBody][Required] IEnumerable categoriesIds) + { + var node = await NodesTreeCollection.Find(new BsonDocument("_id", entityId)).FirstOrDefaultAsync(); + if (node == null) + return NotFound(); + + await Bus.Publish(new + { + Id = Guid.NewGuid(), + EntityId = entityId, + CategoriesIds = categoriesIds, + UserId + }); + + return Accepted(); + } + + /// + /// Get entities by categoryId + /// + /// Category ID + /// Pagination request (pageSize, pageNumber) + /// + [HttpGet("categories/{categoryId}")] + public async Task GetEntitiesByCategoryId(Guid categoryId, PaginationRequest paginationRequest) + { + var result = ElasticClient.Search(s => s + .Index("categories") + .Type("category") + .From((paginationRequest.PageNumber - 1) * paginationRequest.PageSize) + .Take(paginationRequest.PageSize) + .Query(q => q.QueryString(qs => qs.Query(categoryId.ToString())))); + + var list = new PagedList(result.Hits.Select(h => JsonConvert.DeserializeObject(h.Source.Node.ToString())), (int)result.Total, paginationRequest.PageNumber, paginationRequest.PageSize); + + this.AddPaginationHeader(paginationRequest, list, "entities", null, categoryId.ToString()); + + return Ok(list); + } + + /// + /// Get categories ids by entityId + /// + /// Entity ID + /// + [HttpGet("entities/{entityId}/categories")] + [ProducesResponseType(typeof(IEnumerable), 200)] + public async Task GetCategoriesIdsByEntityId(Guid entityId) + { + var node = await NodesTreeCollection.Find(new BsonDocument("_id", entityId)).FirstOrDefaultAsync(); + if (node == null) + return NotFound(); + + var hits = ElasticClient.Search(s => s + .Index("categories") + .Type("category") + .Query(q => q.QueryString(qs => qs.Query(entityId.ToString())))) + .Hits.ToArray(); + + IEnumerable categoriesIds = new List(); + + if (hits.Any()) + { + JObject hitObject = JsonConvert.DeserializeObject(hits[0].Source.ToString()); + categoriesIds = hitObject.Value("CategoriesIds").Select(x => Guid.Parse(x.ToString())); + } + return Ok(categoriesIds); + } + + [NonAction] + public string CreatePageUri(PaginationRequest request, PaginationUriType uriType, string action, Guid? containerId = null, string filter = null, IEnumerable fields = null) + { + int pageNumber = uriType == PaginationUriType.PreviousPage ? request.PageNumber - 1 : request.PageNumber + 1; + + return UrlHelper.Link(action, new { query = filter, pageSize = request.PageSize, pageNumber }); + + } + + [NonAction] + public string CreatePageUri(PaginationRequest request, PaginationUriType uriType, string action, RouteValueDictionary routeValueDictionary) + { + int pageNumber = uriType == PaginationUriType.PreviousPage ? request.PageNumber - 1 : request.PageNumber + 1; + + return UrlHelper.Link(action, routeValueDictionary); + } + } +} diff --git a/Sds.Osdr.WebApi/Controllers/CategoryTreesController.cs b/Sds.Osdr.WebApi/Controllers/CategoryTreesController.cs new file mode 100644 index 0000000..0e02a69 --- /dev/null +++ b/Sds.Osdr.WebApi/Controllers/CategoryTreesController.cs @@ -0,0 +1,392 @@ +using Leanda.Categories.Domain.Commands; +using Leanda.Categories.Domain.ValueObjects; +using MassTransit; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using MongoDB.Bson; +using MongoDB.Driver; +using Sds.Osdr.WebApi.Filters; +using Sds.Osdr.WebApi.Requests; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Sds.Osdr.WebApi.Extensions; +using System.Linq; +using CQRSlite.Domain; +using Leanda.Categories.Domain; +using Microsoft.AspNetCore.JsonPatch; + +namespace Sds.Osdr.WebApi.Controllers +{ + [Route("api/[controller]")] + [Authorize] + [UserInfoRequired] + public class CategoryTreesController : MongoDbController, IPaginationController + { + private IBusControl Bus; + private IMongoCollection _categoryTreeCollection; + private IMongoCollection _nodesCollection; + private readonly ISession _session; + private IUrlHelper _urlHelper; + + public CategoryTreesController(IMongoDatabase database, IBusControl bus, IUrlHelper urlHelper, ISession session) : base(database) + { + Bus = bus ?? throw new ArgumentNullException(nameof(bus)); + _urlHelper = urlHelper ?? throw new ArgumentNullException(nameof(urlHelper)); + _categoryTreeCollection = Database.GetCollection("CategoryTrees"); + _nodesCollection = Database.GetCollection("Nodes"); + _session = session ?? throw new ArgumentNullException(nameof(session)); + } + + /// + /// Returns all available categories + /// + /// + [HttpGet("tree")] + public async Task GetAllCategoryTree([FromQuery]PaginationRequest request) + { + var result = _categoryTreeCollection.Find(_ => true).Project(@"{ + CreatedBy:1, + CreatedDateTime:1, + UpdatedBy:1, + UpdatedDateTime:1, + Version:1 + }"); + + if (request != null) + { + var pagedResult = await result.ToPagedListAsync(request.PageNumber, request.PageSize); + + this.AddPaginationHeader(request, pagedResult, nameof(GetAllCategoryTree), null, null, null); + + return Ok(pagedResult); + } + + return Ok(result.ToListAsync()); + } + + /// + /// Create new category tree + /// + /// + [HttpPost("tree")] + [Authorize(Policy = "Administrator")] + public async Task CreateCategoryTree([FromBody] List nodes) + { + //TODO: Validate input parapeters e.g. title + Guid categoriesTreeId = Guid.NewGuid(); + + nodes.InitNodeIds(); + + await Bus.Publish(new + { + Id = categoriesTreeId, + UserId, + Nodes = nodes + }); + + return CreatedAtRoute("GetCategoriesTree", new { id = categoriesTreeId }, categoriesTreeId.ToString()); + } + + /// + /// Get categories tree by Id + /// + /// Caregories tree aggregate ID + /// + [HttpGet("tree/{id}", Name = "GetCategoriesTree")] + public async Task GetCategoriesTree(Guid id) + { + var tree = await _categoryTreeCollection.Find(new BsonDocument("_id", id)) + .Project(@"{ + CreatedBy:1, + CreatedDateTime:1, + UpdatedBy:1, + UpdatedDateTime:1, + Version:1, + Nodes:1 + }") + .FirstOrDefaultAsync(); + + if (tree == null) + { + return NotFound(); + } + + return Ok(tree); + } + + /// + /// Update categories tree + /// + /// Categories tree ID + /// New categories tree nodes + /// Carrent cattegories tree object version + /// + [HttpPatch("tree/{id}")] + [Authorize(Policy = "Administrator")] + public async Task PatchCategoriesTree(Guid id, [FromBody] JsonPatchDocument request, int version) + { + var treeView = await _categoryTreeCollection.Find(new BsonDocument("_id", id)).FirstOrDefaultAsync(); + + if (treeView == null) + { + return NotFound(); + } + + var operationsCollection = new UpdateCategoryTreeRequest(); + request.ApplyTo(operationsCollection); + + if (operationsCollection.Nodes.Any()) + { + var tree = await _session.Get(id); + var aggregateIds = tree.Nodes.GetNodeIds(); + var requestIds = operationsCollection.Nodes.GetNodeIds(); + + if (!requestIds.All(i => aggregateIds.Contains(i))) + { + var invalidIds = requestIds.Where(i => !aggregateIds.Contains(i)); + + return BadRequest($"Can not find nodes with ids {string.Join(", ", invalidIds)}"); + } + + operationsCollection.Nodes.InitNodeIds(); + + await Bus.Publish(new + { + Id = id, + UserId = UserId, + Nodes = operationsCollection.Nodes, + ExpectedVersion = version + }); + } + + if (operationsCollection.IsDeleted) + { + await Bus.Publish(new + { + Id = id, + UserId, + ExpectedVersion = version + }); + } + + return Accepted(); + } + + /// + /// Update categories tree node + /// + /// Categories tree ID + /// Categories tree node ID + /// New categories tree nodes + /// Carrent cattegories tree object version + /// + [HttpPatch("tree/{id}/{nodeId}")] + [Authorize(Policy = "Administrator")] + public async Task PatchCategoriesTreeNode(Guid id, Guid nodeId, [FromBody] JsonPatchDocument request, int version) + { + //TODO: Validate input parapeters e.g. title + var treeView = await _categoryTreeCollection.Find(new BsonDocument("_id", id)).FirstOrDefaultAsync(); + + if (treeView == null) + { + return NotFound(); + } + + var tree = await _session.Get(id); + + if (!tree.Nodes.ContainsTree(nodeId)) + { + return NotFound(); + } + + var operations = new UpdateCategoryTreeRequest(); + + request.ApplyTo(operations); + + if (operations.Nodes.Any()) + { + operations.Nodes.InitNodeIds(); + + await Bus.Publish(new + { + Id = id, + ParentId = nodeId, + UserId = UserId, + Nodes = operations.Nodes, + ExpectedVersion = version + }); + } + + if (operations.IsDeleted) + { + await Bus.Publish(new + { + Id = id, + NodeId = nodeId, + UserId, + ExpectedVersion = version + }); + } + + return Accepted(); + } + + + /// + /// Update categories tree + /// + /// Categories tree ID + /// New categories tree nodes + /// Carrent cattegories tree object version + /// + [HttpPut("tree/{id}")] + [Authorize(Policy = "Administrator")] + public async Task UpdateCategoriesTree(Guid id, [FromBody] List nodes, int version) + { + var treeView = await _categoryTreeCollection.Find(new BsonDocument("_id", id)).FirstOrDefaultAsync(); + + if (treeView == null) + { + return NotFound(); + } + var tree = await _session.Get(id); + var aggregateIds = tree.Nodes.GetNodeIds(); + var requestIds = nodes.GetNodeIds(); + + if (!requestIds.All(i => aggregateIds.Contains(i))) + { + var invalidIds = requestIds.Where(i => !aggregateIds.Contains(i)); + + return BadRequest($"Can not find nodes with ids {string.Join(", ", invalidIds)}"); + } + + nodes.InitNodeIds(); + + await Bus.Publish(new + { + Id = id, + UserId = UserId, + Nodes = nodes, + ExpectedVersion = version + }); + + return Accepted(); + } + + /// + /// Update categories tree node + /// + /// Categories tree ID + /// Categories tree node ID + /// New categories tree nodes + /// Carrent cattegories tree object version + /// + [HttpPut("tree/{id}/{nodeId}")] + [Authorize(Policy = "Administrator")] + public async Task UpdateCategoriesTreeNode(Guid id, Guid nodeId, [FromBody] List nodes, int version) + { + //TODO: Validate input parapeters e.g. title + var treeView = await _categoryTreeCollection.Find(new BsonDocument("_id", id)).FirstOrDefaultAsync(); + + if (treeView == null) + { + return NotFound(); + } + + var tree = await _session.Get(id); + + if (!tree.Nodes.ContainsTree(nodeId)) + { + return NotFound(); + } + + nodes.InitNodeIds(); + + await Bus.Publish(new + { + Id = id, + ParentId = nodeId, + UserId = UserId, + Nodes = nodes, + ExpectedVersion = version + }); + + return Accepted(); + } + + [HttpDelete("tree/{id}")] + [Authorize(Policy = "Administrator")] + public async Task DeleteCategoriesTreeNode(Guid id, int version) + { + var tree = await _categoryTreeCollection.Find(new BsonDocument("_id", id)).FirstOrDefaultAsync(); + + if (tree == null) + { + return NotFound(); + } + + await Bus.Publish(new + { + Id = id, + UserId, + ExpectedVersion = version + }); + + return Accepted(); + } + + [HttpDelete("tree/{id}/{nodeId}")] + [Authorize(Policy = "Administrator")] + public async Task DeleteCategoriesTreeNode(Guid id, Guid nodeId, int version) + { + var treeView = await _categoryTreeCollection.Find(new BsonDocument("_id", id)).FirstOrDefaultAsync(); + + if (treeView == null) + { + return NotFound(); + } + + var tree = await _session.Get(id); + + if (!tree.Nodes.ContainsTree(nodeId)) + { + return NotFound(); + } + + await Bus.Publish(new + { + Id = id, + NodeId = nodeId, + UserId, + ExpectedVersion = version + }); + + return Accepted(); + } + + [NonAction] + public string CreatePageUri(PaginationRequest request, PaginationUriType uriType, string action, Guid? entityId = null, string filter = null, IEnumerable fields = null) + { + int pageNumber = uriType == PaginationUriType.PreviousPage ? request.PageNumber - 1 : request.PageNumber + 1; + return _urlHelper.Link(action, new RouteValueDictionary + { + { "id", entityId }, + { "pageSize", request.PageSize }, + { "pageNumber", pageNumber }, + { "$filter", filter }, + { "$projection", string.Join(",", fields ?? new string[] { }) } + }); + } + + [NonAction] + public string CreatePageUri(PaginationRequest request, PaginationUriType uriType, string action, RouteValueDictionary routeValueDictionary) + { + int pageNumber = uriType == PaginationUriType.PreviousPage ? request.PageNumber - 1 : request.PageNumber + 1; + + return _urlHelper.Link(action, routeValueDictionary); + } + + } +} diff --git a/Sds.Osdr.WebApi/Controllers/EntititesController.cs b/Sds.Osdr.WebApi/Controllers/EntititesController.cs index b15d1d5..fd9bbfe 100644 --- a/Sds.Osdr.WebApi/Controllers/EntititesController.cs +++ b/Sds.Osdr.WebApi/Controllers/EntititesController.cs @@ -195,15 +195,25 @@ public async Task Get(string type, Guid id, string propertyPath) return NotFound(); } - if (!string.IsNullOrEmpty(propertyPath)) + try { - foreach (var property in propertyPath.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries)) + var comparer = StringComparer.OrdinalIgnoreCase; + result = new Dictionary((IDictionary)result, comparer); + var propertyBreadcrumbs = propertyPath.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); + if (!string.IsNullOrEmpty(propertyPath)) { - result = (((IDictionary)result))[property.ToPascalCase()]; + for (int i = 0; i< propertyBreadcrumbs.Count() - 1; i++ ) + { + result = new Dictionary((IDictionary)result[propertyBreadcrumbs[i]], comparer); + } + return Ok(result[propertyBreadcrumbs.Last()]); } + return NotFound(); + } + catch(Exception e) + { + return NotFound(); } - - return Ok(result); } /// @@ -339,19 +349,10 @@ await _bus.Publish(new return AcceptedAtRoute("GetSingleEntity", new { type = "folders", id = folderId }, null); } - /// - /// Update File - /// - /// File identifier - /// Request with json patch object - /// Version object - /// - /// file updating started - /// new file name is not valid [ProducesResponseType(202)] - [ProducesResponseType(400)] + [ProducesResponseType(404)] [HttpPatch("files/{id}")] - public async Task PatchFile(Guid id, [FromBody]JsonPatchDocument request, int version) + public async Task PatchFile(Guid id, [FromBody]JsonPatchDocument request, int version) { BsonDocument filter = new OrganizeFilter(UserId.Value).ById(id); @@ -364,7 +365,7 @@ public async Task PatchFile(Guid id, [FromBody]JsonPatchDocument< if (permissions is null) return NotFound(); - var file = new UpdatedEntityData() { Id = id }; + var file = new UpdateEntityRequest() { Id = id }; if (permissions.Contains(nameof(AccessPermissions))) file.Permissions = BsonSerializer.Deserialize(permissions[nameof(AccessPermissions)].AsBsonDocument); @@ -389,6 +390,18 @@ await _bus.Publish(new }); } + if (file.Metadata.Any()) + { + Log.Information($"Update metadata for file {id}"); + await _bus.Publish(new + { + file.Metadata, + Id = id, + UserId = UserId.Value, + ExpectedVersion = version + }); + } + if (file.ParentId != Guid.Empty) { Log.Information($"Move file {id} to {file.ParentId}"); @@ -427,7 +440,7 @@ await _bus.Publish(new [ProducesResponseType(202)] [ProducesResponseType(400)] [HttpPatch("folders/{id}")] - public async Task PatchFolder(Guid id, [FromBody]JsonPatchDocument request, int version) + public async Task PatchFolder(Guid id, [FromBody]JsonPatchDocument request, int version) { Log.Information($"Updating folder {id}"); @@ -442,7 +455,7 @@ public async Task PatchFolder(Guid id, [FromBody]JsonPatchDocumen if (folderToUpdate is null) return NotFound(); - var folder = new UpdatedEntityData() { Id = id }; + var folder = new UpdateEntityRequest() { Id = id }; if (folderToUpdate.Contains(nameof(AccessPermissions))) folder.Permissions = BsonSerializer.Deserialize(folderToUpdate[nameof(AccessPermissions)].AsBsonDocument); @@ -507,7 +520,7 @@ await _bus.Publish(new [ProducesResponseType(202)] [ProducesResponseType(400)] [HttpPatch("models/{id}")] - public async Task PatchModel(Guid id, [FromBody]JsonPatchDocument request, int version) + public async Task PatchModel(Guid id, [FromBody]JsonPatchDocument request, int version) { Log.Information($"Updating model {id}"); @@ -522,7 +535,7 @@ public async Task PatchModel(Guid id, [FromBody]JsonPatchDocument if (modelToUpdate is null) return NotFound(); - var model = new UpdatedEntityData() { Id = id }; + var model = new UpdateEntityRequest() { Id = id }; if (modelToUpdate.Contains(nameof(AccessPermissions))) model.Permissions = BsonSerializer.Deserialize(modelToUpdate[nameof(AccessPermissions)].AsBsonDocument); diff --git a/Sds.Osdr.WebApi/Dockerfile b/Sds.Osdr.WebApi/Dockerfile index c6b6c05..dd13183 100644 --- a/Sds.Osdr.WebApi/Dockerfile +++ b/Sds.Osdr.WebApi/Dockerfile @@ -10,6 +10,8 @@ ENV OSDR_MONGO_DB=$CI_MONGO_DB WORKDIR /build +COPY Leanda.Microscopy/Leanda.Microscopy.csproj Leanda.Microscopy/ +COPY Leanda.Categories/Leanda.CategoryTree.csproj Leanda.Categories/ COPY Sds.Osdr.Chemicals/Sds.Osdr.Chemicals.csproj Sds.Osdr.Chemicals/ COPY Sds.Osdr.Crystals/Sds.Osdr.Crystals.csproj Sds.Osdr.Crystals/ COPY Sds.Osdr.Domain/Sds.Osdr.Domain.csproj Sds.Osdr.Domain/ @@ -29,6 +31,8 @@ COPY Nuget.config . RUN dotnet restore --configfile Nuget.config Sds.Osdr.WebApi/Sds.Osdr.WebApi.csproj +COPY Leanda.Microscopy Leanda.Microscopy +COPY Leanda.Categories Leanda.Categories COPY Sds.Osdr.Chemicals Sds.Osdr.Chemicals COPY Sds.Osdr.Crystals Sds.Osdr.Crystals COPY Sds.Osdr.Domain Sds.Osdr.Domain diff --git a/Sds.Osdr.WebApi/Program.cs b/Sds.Osdr.WebApi/Program.cs index 3ab2e33..838ebb5 100644 --- a/Sds.Osdr.WebApi/Program.cs +++ b/Sds.Osdr.WebApi/Program.cs @@ -19,7 +19,6 @@ public static void Main(string[] args) .UseConfiguration(configuration) .UseIISIntegration() .UseStartup() - //.UseApplicationInsights() .Build(); host.Run(); diff --git a/Sds.Osdr.WebApi/Properties/launchSettings.json b/Sds.Osdr.WebApi/Properties/launchSettings.json index 86bf5e9..a99c996 100644 --- a/Sds.Osdr.WebApi/Properties/launchSettings.json +++ b/Sds.Osdr.WebApi/Properties/launchSettings.json @@ -3,7 +3,7 @@ "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { - "applicationUrl": "http://localhost:28610/", + "applicationUrl": "http://localhost:28611/", "sslPort": 0 } }, diff --git a/Sds.Osdr.WebApi/Requests/PatchEntityRequest.cs b/Sds.Osdr.WebApi/Requests/PatchEntityRequest.cs index b460df4..5fad8d6 100644 --- a/Sds.Osdr.WebApi/Requests/PatchEntityRequest.cs +++ b/Sds.Osdr.WebApi/Requests/PatchEntityRequest.cs @@ -8,7 +8,7 @@ namespace Sds.Osdr.WebApi.Requests { public class PatchEntityRequest { - public JsonPatchDocument PatchDocument { get; set; } + public JsonPatchDocument PatchDocument { get; set; } public int Version { get; set; } } } diff --git a/Sds.Osdr.WebApi/Requests/UpdateCategoryTreeRequest.cs b/Sds.Osdr.WebApi/Requests/UpdateCategoryTreeRequest.cs new file mode 100644 index 0000000..fbf915b --- /dev/null +++ b/Sds.Osdr.WebApi/Requests/UpdateCategoryTreeRequest.cs @@ -0,0 +1,14 @@ +using Leanda.Categories.Domain.ValueObjects; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sds.Osdr.WebApi.Requests +{ + public class UpdateCategoryTreeRequest + { + public bool IsDeleted { get; set; } = false; + public IList Nodes { get; set; } = new List(); + } +} diff --git a/Sds.Osdr.WebApi/Requests/UpdatedEntityData.cs b/Sds.Osdr.WebApi/Requests/UpdatedEntityData.cs index fbbb6ce..39fa6d9 100644 --- a/Sds.Osdr.WebApi/Requests/UpdatedEntityData.cs +++ b/Sds.Osdr.WebApi/Requests/UpdatedEntityData.cs @@ -1,23 +1,26 @@ -using MongoDB.Bson.Serialization.Attributes; +using Sds.Osdr.Domain; using Sds.Osdr.Generic.Domain.ValueObjects; using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace Sds.Osdr.WebApi.Requests { - public class UpdatedEntityData + public class UpdateEntityRequest { public Guid Id { get; set; } - public UpdatedEntityData() => Permissions = new AccessPermissions + public UpdateEntityRequest() { - Users = new HashSet(), - Groups = new HashSet() - }; + Permissions = new AccessPermissions + { + Users = new HashSet(), + Groups = new HashSet() + }; + Metadata = new List>(); + } public Guid? ParentId { get; set; } = Guid.Empty; public string Name { get; set; } = null; public AccessPermissions Permissions { get; set; } + public IEnumerable> Metadata { get; set; } } } diff --git a/Sds.Osdr.WebApi/Sds.Osdr.WebApi.csproj b/Sds.Osdr.WebApi/Sds.Osdr.WebApi.csproj index 1d790b7..ddac3e2 100644 --- a/Sds.Osdr.WebApi/Sds.Osdr.WebApi.csproj +++ b/Sds.Osdr.WebApi/Sds.Osdr.WebApi.csproj @@ -50,8 +50,10 @@ + + @@ -74,6 +76,7 @@ + diff --git a/Sds.Osdr.WebApi/Startup.cs b/Sds.Osdr.WebApi/Startup.cs index 537d153..9903fb6 100644 --- a/Sds.Osdr.WebApi/Startup.cs +++ b/Sds.Osdr.WebApi/Startup.cs @@ -48,6 +48,11 @@ using System.Net.WebSockets; using System.Threading; using Microsoft.AspNetCore.Http.Features; +using CQRSlite.Events; +using CQRSlite.Domain; +using ISession = CQRSlite.Domain.ISession; +using Sds.CqrsLite.EventStore; +using Microsoft.AspNetCore.Identity; namespace Sds.Osdr.WebApi { @@ -145,7 +150,12 @@ public void ConfigureServices(IServiceCollection services) try { var settings = Environment.ExpandEnvironmentVariables(Configuration["EventStore:ConnectionString"]); - services.AddSingleton(new EventStore.EventStore(settings)); + services.AddSingleton(new EventStore.EventStore(settings)); + + services.AddSingleton(y => new GetEventStore(Environment.ExpandEnvironmentVariables(Configuration["EventStore:ConnectionString"]))); + services.AddSingleton(); + services.AddTransient(); + services.AddSingleton(); } catch (Exception e) { @@ -156,18 +166,14 @@ public void ConfigureServices(IServiceCollection services) //services.AddScoped(s => s.GetService().GetDatabase()); try { - var connectionString = Environment.ExpandEnvironmentVariables(Configuration["OsdrConnectionSettings:ConnectionString"]); - services.AddTransient( - x => new GridFsStorage(connectionString, Configuration["OsdrConnectionSettings:DatabaseName"]) - ); - Log.Information($"Connecting to MongoDB {connectionString}"); - var mongoClient = new MongoClient(connectionString); - services.AddSingleton(mongoClient); - var database = Configuration["OsdrConnectionSettings:DatabaseName"]; - Log.Information($"Using to MongoDB database {database}"); - services.AddSingleton(service => service.GetService().GetDatabase(database)); - services.AddTransient(service => new OrganizeDataProvider(mongoClient.GetDatabase(database), service.GetService())); - + var mongoConnectionString = Environment.ExpandEnvironmentVariables(Configuration["OsdrConnectionSettings:ConnectionString"]); + var mongoUrl = new MongoUrl(mongoConnectionString); + + Log.Information($"Connecting to MongoDB {mongoConnectionString}"); + services.AddTransient(x => new GridFsStorage(x.GetService())); + services.AddSingleton(new MongoClient(mongoUrl)); + services.AddSingleton(service => service.GetService().GetDatabase(mongoUrl.DatabaseName)); + services.AddTransient(service => new OrganizeDataProvider(service.GetService(), service.GetService())); } catch (Exception ex) { @@ -261,6 +267,7 @@ public void ConfigureServices(IServiceCollection services) } }; }); + services.AddAuthorization(options => options.AddPolicy("Administrator", policy => policy.RequireClaim("user_role", "leanda-admin"))); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -306,7 +313,8 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF // appBuilder.ServerFeatures.Get().MaxRequestBodySize = fvcSettings.MaxFileSize; //}); - var routePrefixes = new[] { "/api/entities", "/api/nodes", "/api/usernotifications", "/api/public", "/api/machinelearning/predictions" }; + var routePrefixes = new[] { "/api/entities", "/api/nodes", "/api/usernotifications", "/api/public", "/api/machinelearning/predictions", + "/api/categoryentities", "/api/categorytrees"}; var binaryDataEndpints = new[] { "/blobs/", "/images/", ".zip" }; app.UseWhen(context => (routePrefixes.Any(context.Request.Path.Value.ToLower().Contains) && !binaryDataEndpints.Any(context.Request.Path.Value.ToLower().Contains)), diff --git a/Sds.Osdr.WebApi/appsettings.json b/Sds.Osdr.WebApi/appsettings.json index 7799cd8..837166c 100644 --- a/Sds.Osdr.WebApi/appsettings.json +++ b/Sds.Osdr.WebApi/appsettings.json @@ -1,7 +1,6 @@ { "OsdrConnectionSettings": { "ConnectionString": "%OSDR_MONGO_DB%", - "DatabaseName": "osdr_dev" }, "Redis": { "ConnectionString": "%OSDR_REDIS%", diff --git a/docker-compose.debug.yml b/docker-compose.debug.yml new file mode 100644 index 0000000..cca9d4a --- /dev/null +++ b/docker-compose.debug.yml @@ -0,0 +1,194 @@ +version: '3.4' + +services: + eventstore: + image: eventstore/eventstore:release-4.0.2 + ports: + - "2113:2113" + - "1113:1113" + environment: + - RUN_PROJECTIONS=All + networks: + - leanda-net + + redis: + image: redis:4-alpine + command: redis-server --appendonly yes + ports: + - "6379:6379" + networks: + - leanda-net + + rabbitmq: + image: leanda/rabbitmq + hostname: "leanda" + environment: + - RABBITMQ_DEFAULT_VHOST=leanda + ports: + - "8282:15672" + - "5672:5672" + networks: + - leanda-net + + mongo: + image: mongo:3.6 + ports: + - "27017:27017" + networks: + - leanda-net + + imaging: + container_name: imaging + image: leanda/imaging:${TAG_VERSION-latest} + entrypoint: /bin/bash + environment: + - OSDR_LOG_FOLDER=/logs + - OSDR_TEMP_FILES_FOLDER=/temp + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda + - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev + - QUEUE_PREFETCH_SIZE=9 + - EXECUTOR_THREAD_COUNT=3 + command: ./wait-for-it.sh rabbitmq:5672 -t 60 -- java -Djava.awt.headless=true -Xmx256m -XX:NativeMemoryTracking=summary -jar sds-imaging-service.jar + volumes: + - ${OSDR_LOG_FOLDER}:/logs + - ${OSDR_TEMP_FILES_FOLDER}:/temp + networks: + - leanda-net + depends_on: + - rabbitmq + - mongo + + chemical-file-parser: + container_name: chemical-file-parser + image: leanda/chemical-file-parser:${TAG_VERSION-latest} + entrypoint: /bin/bash + environment: + - TZ=EST + - OSDR_LOG_FOLDER=/logs + - OSDR_TEMP_FILES_FOLDER=/temp + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda + - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev + - QUEUE_PREFETCH_SIZE=9 + - EXECUTOR_THREAD_COUNT=3 + command: ./wait-for-it.sh rabbitmq:5672 -t 60 -- ./wait-for-it.sh mongo:27017 -t 60 -- java -jar chemical-parser.jar + volumes: + - ${OSDR_LOG_FOLDER}:/logs + - ${OSDR_TEMP_FILES_FOLDER}:/temp + networks: + - leanda-net + depends_on: + - rabbitmq + - mongo + + chemical-properties: + container_name: chemical-properties + image: leanda/chemical-properties:${TAG_VERSION-latest} + entrypoint: /bin/bash + environment: + - TZ=EST + - OSDR_LOG_FOLDER=/logs + - OSDR_TEMP_FILES_FOLDER=/temp + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda + - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev + - QUEUE_PREFETCH_SIZE=9 + - EXECUTOR_THREAD_COUNT=3 + command: ./wait-for-it.sh rabbitmq:5672 -t 60 -- ./wait-for-it.sh mongo:27017 -t 60 -- java -jar sds-chemical-properties-service.jar + volumes: + - ${OSDR_LOG_FOLDER}:/logs + - ${OSDR_TEMP_FILES_FOLDER}:/temp + ports: + - 8986:8086 + networks: + - leanda-net + depends_on: + - rabbitmq + - mongo + + metadata-processing: + container_name: metadata-processing + image: leanda/metadata-processing:${TAG_VERSION-latest} + entrypoint: /bin/bash + environment: + - OSDR_LOG_FOLDER=/logs + - OSDR_MONGO_DB=mongodb://mongo:27017/osdr + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda + command: ./wait-for-it.sh rabbitmq:5672 -t 60 -- ./Sds.MetadataStorage.Processing + volumes: + - ${OSDR_LOG_FOLDER}:/logs + networks: + - leanda-net + ports: + - "11050:11050" + depends_on: + - rabbitmq + + microscopy-metadata-service: + container_name: microscopy-metadata + image: leanda/microscopy-metadata:${TAG_VERSION-latest} + entrypoint: /bin/bash + environment: + - TZ=EST + - OSDR_LOG_FOLDER=/logs + - OSDR_TEMP_FILES_FOLDER=/temp + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda + - OSDR_MONGO_DB=mongodb://mongo:27017/osdr_dev + - QUEUE_PREFETCH_SIZE=9 + - EXECUTOR_THREAD_COUNT=3 + command: ./wait-for-it.sh rabbitmq:5672 -t 60 -- ./wait-for-it.sh mongo:27017 -t 60 -- java -XX:NativeMemoryTracking=summary -jar leanda-microscopy-metadata-service.jar + volumes: + - ${OSDR_LOG_FOLDER}:/logs/ + - ${OSDR_TEMP_FILES_FOLDER}:/temp/ + # ports: + # - 8986:8090 + networks: + - leanda-net + depends_on: + - rabbitmq + - mongo + + blob-storage-api: + container_name: blob-storage-api + image: leanda/blob-storage-webapi + entrypoint: /bin/bash + environment: + - IDENTITY_SERVER_URL=${IDENTITY_SERVER_URL} + - OSDR_LOG_FOLDER=/logs + - OSDR_MONGO_DB=mongodb://mongo:27017/osdr + - OSDR_RABBIT_MQ=rabbitmq://guest:guest@rabbitmq:5672/leanda + - SWAGGER_BASEPATH=/blob/v1 + - OSDR_LOG_LEVEL=${OSDR_LOG_LEVEL} + command: ./wait-for-it.sh rabbitmq:15672 -t 30 -- ./Sds.Storage.Blob.WebApi + volumes: + - ${OSDR_LOG_FOLDER}:/logs + networks: + - leanda-net + ports: + - "18006:18006" + + ui: + container_name: ui + image: leanda/ui:${TAG_VERSION-latest} + environment: + - IDENTITY_SERVER_URL=${IDENTITY_SERVER_URL} + - CORE_API_URL=${CORE_API_URL} + - BLOB_STORAGE_API_URL=${BLOB_STORAGE_API_URL} + - IMAGING_URL=${IMAGING_URL} + - SIGNALR_URL=${SIGNALR_URL} + - METADATA_URL=${METADATA_URL} + - PROXY_JSMOL_URL=${PROXY_JSMOL_URL} + - KETCHER_URL=${KETCHER_URL} + - REALM=${REALM} + networks: + - leanda-net + ports: + - "5555:80" + depends_on: + - blob-storage-api + +networks: + leanda-net: + +#volumes: +# test-results: +# external: true +# name: test-results \ No newline at end of file diff --git a/rebuild-all-images.ps1 b/rebuild-all-images.ps1 index 42f11ba..7b4d35c 100644 --- a/rebuild-all-images.ps1 +++ b/rebuild-all-images.ps1 @@ -1,8 +1,9 @@ -docker build -t docker.your-company.com/osdr-service-persistence:ci-local -f Sds.Osdr.Persistence/Dockerfile . -docker build -t docker.your-company.com/osdr-service-frontend:ci-local -f Sds.Osdr.Domain.FrontEnd/Dockerfile . -docker build -t docker.your-company.com/osdr-service-backend:ci-local -f Sds.Osdr.Domain.BackEnd/Dockerfile . -docker build -t docker.your-company.com/osdr-service-sagahost:ci-local -f Sds.Osdr.Domain.SagaHost/Dockerfile . -docker build -t docker.your-company.com/osdr-service-web-api:ci-local -f Sds.Osdr.WebApi/Dockerfile . -docker build -t docker.your-company.com/osdr-service-integration:ci-local -f Sds.Osdr.IntegrationTests/Dockerfile . -docker build -t docker.your-company.com/osdr-service-webapi-integration:ci-local -f Sds.Osdr.WebApi.IntegrationTests/Dockerfile . -docker image ls docker.your-company.com/osdr-service-* \ No newline at end of file +docker build -t leanda/core-persistence:latest -f Sds.Osdr.Persistence/Dockerfile . +docker build -t leanda/core-frontend:latest -f Sds.Osdr.Domain.FrontEnd/Dockerfile . +docker build -t leanda/core-backend:latest -f Sds.Osdr.Domain.BackEnd/Dockerfile . +docker build -t leanda/core-sagahost:latest -f Sds.Osdr.Domain.SagaHost/Dockerfile . +docker build -t leanda/core-web-api:latest -f Sds.Osdr.WebApi/Dockerfile . +docker build -t leanda/integration:latest -f Sds.Osdr.IntegrationTests/Dockerfile . +docker build -t leanda/webapi-integration:latest -f Sds.Osdr.WebApi.IntegrationTests/Dockerfile . +docker build -t leanda/e2e-tests:latest -f Sds.Osdr.EndToEndTests/Dockerfile . +docker image ls leanda/* \ No newline at end of file