public void GetIoTAlertShouldreturnEmptywhenAlertdoesntexists()
        {
            // mock RetrieveMultiple call to retrieve an empty entity collection
            EntityCollection queryExpressionResult = new EntityCollection();

            this.cdsServiceClientMock.Setup(service => service.RetrieveMultiple(It.IsAny <QueryBase>())).Returns(queryExpressionResult);
            Guid alertId = Guid.Empty;

            // create DynamicsEntityService to test
            DynamicsEntityService dynamicsEntityService = new DynamicsEntityService(this.cdsServiceClientMock.Object);

            // call GetIoTAlert
            IconicsFault iconicsFault = new IconicsFault()
            {
                FaultName       = "TestFault",
                AssetName       = "TestAsset",
                AssetPath       = "TestAssetPath",
                FaultState      = "Active",
                FaultActiveTime = "2020-08-13T20:01:04.6565528Z",
            };

            Guid returnedId = dynamicsEntityService.GetIoTAlert(iconicsFault, this.loggerMock);

            // verify that Empty is returned and correct message is logged
            Assert.Equal(alertId, returnedId);
            string expectedLogText = "An Existing IoT Alert with Alert Token";

            Assert.Contains(expectedLogText, this.loggerMock.Logs[0], StringComparison.OrdinalIgnoreCase);
        }
Beispiel #2
0
        /// <summary>
        /// Method definition for creating an IoT Alert in Dynamics.
        /// </summary>
        /// <param name="iconicsFault">Fault Data.</param>
        /// <param name="assetId">Customer Asset ID.</param>
        /// <param name="log">Log Content.</param>
        /// <returns>IoT Alert record GUID.</returns>
        public Guid CreateAlert(IconicsFault iconicsFault, Guid assetId, ILogger log)
        {
            Guid alertId = Guid.Empty;

            if (iconicsFault != null)
            {
                // create new alert from fault data
                Entity newAlert = new Entity(DynamicsEntities.IoTAlert);
                newAlert[IoTAlertProperties.MsdynAlertData]   = JsonConvert.SerializeObject(iconicsFault);
                newAlert[IoTAlertProperties.MsdynAlertTime]   = DateTimeOffset.Parse(iconicsFault.FaultActiveTime, CultureInfo.CurrentCulture);
                newAlert[IoTAlertProperties.MsdynAlertType]   = IotAlertAlertType.Anomaly;
                newAlert[IoTAlertProperties.MsdynDescription] = iconicsFault.FaultName;
                newAlert[IoTAlertProperties.StateCode]        = IotAlertStateCode.Active;
                newAlert[IoTAlertProperties.StatusCode]       = IotAlertStatusCode.Active;
                newAlert[DynamicsRelationships.IoTAlertToOneCustomerAsset] = new EntityReference(DynamicsEntities.CustomerAsset, assetId);
                newAlert[IoTAlertProperties.MsdynAlertToken] = DynamicsEntityService.ComputeSha256Hash(iconicsFault);

                alertId = GetExecutionPolicy(log).Execute(() =>
                {
                    return(this.cdsServiceClient.Create(newAlert));
                });

                log.LogInformation(
                    "Created Alert {alertId} from Fault (FaultName: {FaultName}, FaultActiveTime: {FaultActiveTime}, AssetName: {AssetName}, AssetPath: {AssetPath}).",
                    alertId,
                    iconicsFault.FaultName,
                    iconicsFault.FaultActiveTime,
                    iconicsFault.AssetName,
                    iconicsFault.AssetPath);

                log.LogInformation("Associated Alert {alertId} with Asset {assetId}.", alertId, assetId);
            }

            return(alertId);
        }
        public void CreateAlertShouldReturnNewId()
        {
            // mock Create call to retrieve a known alert id
            Guid alertId = Guid.NewGuid();

            this.cdsServiceClientMock.Setup(service => service.Create(It.IsAny <Entity>())).Returns(alertId);

            // create DynamicsEntityService to test
            DynamicsEntityService dynamicsEntityService = new DynamicsEntityService(this.cdsServiceClientMock.Object);

            // call CreateAlert
            Guid         assetId      = Guid.NewGuid();
            IconicsFault iconicsFault = new IconicsFault()
            {
                FaultName       = "TestFault",
                AssetName       = "TestAsset",
                AssetPath       = "TestAssetPath",
                FaultState      = "Active",
                FaultActiveTime = "2020-08-13T20:01:04.6565528Z",
            };
            Guid returnedId = dynamicsEntityService.CreateAlert(iconicsFault, assetId, this.loggerMock);

            this.cdsServiceClientMock.Verify(x => x.Create(It.IsAny <Entity>()), Times.Once);

            Assert.Equal(
                $"Created Alert {returnedId} from Fault (FaultName: {iconicsFault.FaultName}, FaultActiveTime: {iconicsFault.FaultActiveTime}, AssetName: {iconicsFault.AssetName}, AssetPath: {iconicsFault.AssetPath}).",
                this.loggerMock.Logs[0]);

            Assert.Equal($"Associated Alert {returnedId} with Asset {assetId}.", this.loggerMock.Logs[1]);

            // verify that returned id is equal to the generated entity id
            Assert.Equal(alertId, returnedId);
        }
        public void CreateAssetIfNotExistsShouldReturnEntityIdWhenAssetExists()
        {
            // mock RetrieveMultiple call to retrieve an entity with a known id
            Guid             assetId = Guid.NewGuid();
            EntityCollection queryExpressionResult = new EntityCollection();

            queryExpressionResult.Entities.Add(new Entity()
            {
                Id = assetId
            });
            this.cdsServiceClientMock.Setup(service => service.RetrieveMultiple(It.IsAny <QueryBase>())).Returns(queryExpressionResult);

            // create DynamicsEntityService to test
            DynamicsEntityService dynamicsEntityService = new DynamicsEntityService(this.cdsServiceClientMock.Object);

            // call CreateAssetIfNotExists
            IconicsFault iconicsFault = new IconicsFault()
            {
                AssetName = "TestAsset",
            };

            Guid returnedId = dynamicsEntityService.CreateAssetIfNotExists(iconicsFault, this.loggerMock);

            this.cdsServiceClientMock.Verify(x => x.RetrieveMultiple(It.IsAny <QueryBase>()), Times.Once);

            Assert.Equal($"Retrieved Asset {returnedId}.", this.loggerMock.Logs[0]);

            // verify that returned id is equal to the generated entity id
            Assert.Equal(assetId, returnedId);
        }
Beispiel #5
0
        public async void ShouldRunSuccessfullyOnValidInActiveFault()
        {
            // mock IsValidFault and IsCdsServiceReady calls to return true
            string validationMessage;

            this.validationServiceMock.Setup(service => service.IsValidFault(It.IsAny <IconicsFault>(), out validationMessage)).Returns(true);
            this.dynamicsEntityServiceMock.Setup(service => service.IsCdsServiceReady()).Returns(true);

            var durableClientMock = new Mock <IDurableEntityClient>();

            // create message to pass to Run method
            IconicsFault iconicsFault = new IconicsFault()
            {
                FaultName            = "FaultName",
                AssetName            = "AssetName",
                AssetPath            = "AssetPath",
                FaultState           = "InActive",
                FaultActiveTime      = "2020-08-13T20:01:04.6565528Z",
                EventEnqueuedUtcTime = DateTime.UtcNow.ToString("o", CultureInfo.CurrentCulture),
            };

            string  messageBody = JsonConvert.SerializeObject(iconicsFault);
            Message message     = new Message(Encoding.UTF8.GetBytes(messageBody));

            // instantiate function class and run function
            using (TelemetryConfiguration tc = new TelemetryConfiguration(INSTRUMENTATIONID))
            {
                // instantiate function class and run function
                CreateAlertFromIconicsFault function = new CreateAlertFromIconicsFault(tc, this.dynamicsEntityServiceMock.Object, this.validationServiceMock.Object, this.errorQueueServiceMock.Object, this.instanceId, this.httpClientMock.Object);
                await function.Run(message, this.logger).ConfigureAwait(false);
            }

            // verify that alert is updated
            this.dynamicsEntityServiceMock.Verify(service => service.UpdateIoTAlert(It.IsAny <IconicsFault>(), It.IsAny <ILogger>(), It.IsAny <int>(), It.IsAny <int>()), Times.Once);
        }
        public void GetIoTAlertShouldReturnAlertId()
        {
            // mock RetrieveMultiple call to retrieve an entity with a known id
            Guid             alertId = Guid.NewGuid();
            EntityCollection queryExpressionResult = new EntityCollection();

            queryExpressionResult.Entities.Add(new Entity()
            {
                Id = alertId
            });
            this.cdsServiceClientMock.Setup(service => service.RetrieveMultiple(It.IsAny <QueryBase>())).Returns(queryExpressionResult);

            // create DynamicsEntityService to test
            DynamicsEntityService dynamicsEntityService = new DynamicsEntityService(this.cdsServiceClientMock.Object);

            // call GetIoTAlert
            IconicsFault iconicsFault = new IconicsFault()
            {
                FaultName       = "TestFault",
                AssetName       = "TestAsset",
                AssetPath       = "TestAssetPath",
                FaultState      = "Active",
                FaultActiveTime = "2020-08-13T20:01:04.6565528Z",
            };

            Guid returnedId = dynamicsEntityService.GetIoTAlert(iconicsFault, this.loggerMock);

            // verify that returned id is equal to the entity id
            Assert.Equal(alertId, returnedId);
        }
Beispiel #7
0
        public async void ShouldThrowErrorOnValidFaultWhenCdsServiceIsNotReady()
        {
            // mock IsValidFault call to return true, but IsCdsServiceReady to return false
            string validationMessage;

            this.validationServiceMock.Setup(service => service.IsValidFault(It.IsAny <IconicsFault>(), out validationMessage)).Returns(true).Verifiable();
            this.dynamicsEntityServiceMock.Setup(service => service.IsCdsServiceReady()).Returns(false).Verifiable();

            // create message to pass to Run method
            IconicsFault iconicsFault = new IconicsFault()
            {
                FaultName = "FaultName",
                AssetName = "AssetName",
                AssetPath = "AssetPath",
            };

            string  messageBody = JsonConvert.SerializeObject(iconicsFault);
            Message message     = new Message(Encoding.UTF8.GetBytes(messageBody))
            {
                MessageId = Guid.NewGuid().ToString()
            };

            using FakeTelemetryChannel fakeTelemetryChannel = new FakeTelemetryChannel();

            using TelemetryConfiguration telemetryConfiguration = new TelemetryConfiguration(INSTRUMENTATIONID, fakeTelemetryChannel);

            using HttpResponseMessage httpResponseMsg = new HttpResponseMessage { StatusCode = HttpStatusCode.Accepted };

            // Setting up a mock message handler to ensure that HttpClient.PostAsJsonAsync is called.
            // PostAsJsonAsync is an extension method, and thus cannot use normal setup/verify steps.
            // Using a mock messge handler to verify the handler's SendAsync method is called.
            var mockHttpMessageHandler = new Mock <HttpMessageHandler>();

            mockHttpMessageHandler
            .Protected()
            .Setup <Task <HttpResponseMessage> >("SendAsync", ItExpr.IsAny <HttpRequestMessage>(), ItExpr.IsAny <CancellationToken>())
            .ReturnsAsync(httpResponseMsg).Verifiable();

            using HttpClient httpClient = new HttpClient(mockHttpMessageHandler.Object);

            // instantiate function class and run function
            CreateAlertFromIconicsFault function = new CreateAlertFromIconicsFault(
                telemetryConfiguration,
                this.dynamicsEntityServiceMock.Object,
                this.validationServiceMock.Object,
                this.errorQueueServiceMock.Object,
                this.instanceId,
                httpClient);

            await Assert.ThrowsAsync <ApplicationException>(async() => await function.Run(message, this.logger).ConfigureAwait(false)).ConfigureAwait(false);

            // verify that error is thrown and that correct error is logged
            string expectedErrorText = "Failed to connect to the CDS";

            Assert.Contains(expectedErrorText, this.logger.Logs[1], StringComparison.OrdinalIgnoreCase);

            Mock.Verify();
        }
Beispiel #8
0
        public async Task SendIconicsFaultDataToIoTHubAsync(DeviceClient deviceClient, string uniqueAssetName, string uniqueAssetPath, string uniqueFaultName, string faultState, string faultActiveTime)
        {
            IconicsFault iconicsFault  = GenerateFaultData(uniqueAssetName, uniqueAssetPath, uniqueFaultName, faultState, faultActiveTime);
            string       messageString = JsonConvert.SerializeObject(iconicsFault);

            using (var message = new Microsoft.Azure.Devices.Client.Message(Encoding.ASCII.GetBytes(messageString)))
            {
                await deviceClient.SendEventAsync(message).ConfigureAwait(false);
            }
        }
Beispiel #9
0
        public void ValidateWorkOrderAckShouldReturnFalseIfFaultIsNull()
        {
            string            validationMessage = string.Empty;
            ValidationService validationService = new ValidationService();
            IconicsFault      iconicsFault      = null;
            string            errorMessage;
            string            expectedMessage = "Service Bus Topic message is null.";
            bool isValid = validationService.IsValidFault(iconicsFault, out errorMessage);

            Assert.Equal(expectedMessage, errorMessage);
            Assert.False(isValid);
        }
Beispiel #10
0
        /// <summary>
        /// Method declaration for getting previous IoT Alert based on Alert Token Hash Value.
        /// </summary>
        /// <param name="iconicsFault">Fault Data.</param>
        /// <param name="log">Log Content.</param>
        /// <returns>An existing IoT Alert record GUID.</returns>
        public Guid GetIoTAlert(IconicsFault iconicsFault, ILogger log)
        {
            Guid alertId = Guid.Empty;

            if (iconicsFault != null)
            {
                string alertToken = DynamicsEntityService.ComputeSha256Hash(iconicsFault);

                // build query expression that finds the Iot Alert with the Hash value supplied in the fault
                QueryExpression queryExpression = new QueryExpression()
                {
                    Distinct   = false,
                    EntityName = DynamicsEntities.IoTAlert,
                    ColumnSet  = new ColumnSet(IoTAlertProperties.MsdynAlertToken),
                    Criteria   =
                    {
                        Filters            =
                        {
                            new FilterExpression
                            {
                                Conditions =
                                {
                                    new ConditionExpression(IoTAlertProperties.MsdynAlertToken, ConditionOperator.Equal,    alertToken),
                                    new ConditionExpression(IoTAlertProperties.StateCode,       ConditionOperator.NotEqual, IotAlertStateCode.Inactive.ToString()),
                                },
                            },
                        },
                    },
                };

                // run the query to determine if the IoT Alert already exists in Dynamics
                EntityCollection queryExpressionResult = GetExecutionPolicy(log).Execute(() =>
                {
                    return(this.cdsServiceClient.RetrieveMultiple(queryExpression));
                });

                if (queryExpressionResult.Entities.Count == 0)
                {
                    // IoT does not exist, update log to Application Insights
                    log.LogWarning($"An Existing IoT Alert with Alert Token {alertToken} not found in Dynamics");
                    alertId = Guid.Empty;
                }
                else
                {
                    // IoT Alert does exist, Update IoT Alert Status to InActive and Alert Data with latest Iconic Fault Data
                    alertId = queryExpressionResult.Entities[0].Id;
                }
            }

            return(alertId);
        }
Beispiel #11
0
        public async void ShouldRecordIoTAlerCreatedTelemetryEvent()
        {
            // mock IsValidFault call to return true, but IsCdsServiceReady to return false
            string validationMessage;

            this.validationServiceMock.Setup(service => service.IsValidFault(It.IsAny <IconicsFault>(), out validationMessage)).Returns(true);
            this.dynamicsEntityServiceMock.Setup(service => service.IsCdsServiceReady()).Returns(true);

            var durableClientMock = new Mock <IDurableEntityClient>();

            using FakeTelemetryChannel fakeTelemetryChannel = new FakeTelemetryChannel();

            using TelemetryConfiguration telemetryConfiguration = new TelemetryConfiguration(INSTRUMENTATIONID, fakeTelemetryChannel);

            // create message to pass to Run method
            IconicsFault iconicsFault = new IconicsFault()
            {
                FaultName            = "FaultName",
                FaultActiveTime      = "2020-08-13T20:01:04.6565528Z",
                AssetName            = "AssetName",
                AssetPath            = "AssetPath",
                EventEnqueuedUtcTime = "2020-08-13T20:02:04.6565528Z",
            };

            string  messageBody = JsonConvert.SerializeObject(iconicsFault);
            Message message     = new Message(Encoding.UTF8.GetBytes(messageBody));

            // Instantiate function class and run function
            CreateAlertFromIconicsFault function = new CreateAlertFromIconicsFault(telemetryConfiguration, this.dynamicsEntityServiceMock.Object, this.validationServiceMock.Object, this.errorQueueServiceMock.Object, this.instanceId, this.httpClientMock.Object);

            await function.Run(message, this.logger).ConfigureAwait(false);

            string msg;

            this.validationServiceMock.Verify(x => x.IsValidFault(It.IsAny <IconicsFault>(), out msg), Times.Once);
            this.dynamicsEntityServiceMock.Verify(x => x.IsCdsServiceReady(), Times.Once);

            Assert.Single(fakeTelemetryChannel.SentEventTelemetries);

            if (fakeTelemetryChannel.SentEventTelemetries.Any())
            {
                var eventTelemetry = fakeTelemetryChannel.SentEventTelemetries.ToList()[0];

                Assert.NotNull(eventTelemetry);
                Assert.Equal("IoTAlertCreated", eventTelemetry.Name);

                Assert.Single(eventTelemetry.Metrics);
                Assert.True(eventTelemetry.Metrics.ContainsKey("AlertCreatedElapsedTimeMs"));
            }
        }
Beispiel #12
0
        public void ValidateIconicsFaultShouldReturnNullIfValid()
        {
            ValidationService validationService = new ValidationService();
            IconicsFault      iconicsFault      = new IconicsFault()
            {
                AssetName             = "AssetName",
                AssetPath             = "AssetPath",
                FaultName             = "FaultName",
                FaultActiveTime       = "FaultActiveTime",
                FaultCostValue        = "FaultCostValue",
                RelatedValue1         = "RelatedValue1",
                RelatedValue2         = "RelatedValue2",
                RelatedValue3         = "RelatedValue3",
                RelatedValue4         = "RelatedValue4",
                RelatedValue5         = "RelatedValue5",
                RelatedValue6         = "RelatedValue6",
                RelatedValue7         = "RelatedValue7",
                RelatedValue8         = "RelatedValue8",
                RelatedValue9         = "RelatedValue9",
                RelatedValue10        = "RelatedValue10",
                RelatedValue11        = "RelatedValue11",
                RelatedValue12        = "RelatedValue12",
                RelatedValue13        = "RelatedValue13",
                RelatedValue14        = "RelatedValue14",
                RelatedValue15        = "RelatedValue15",
                RelatedValue16        = "RelatedValue16",
                RelatedValue17        = "RelatedValue17",
                RelatedValue18        = "RelatedValue18",
                RelatedValue19        = "RelatedValue19",
                RelatedValue20        = "RelatedValue20",
                MessageSource         = "MessageSource",
                Description           = "Description",
                EventProcessedUtcTime = "EventProcessedUtcTime",
                PartitionId           = 1,
                EventEnqueuedUtcTime  = "EventEnqueuedUtcTime",
                IoTHub = new IoTHubFaultData()
                {
                    MessageId     = "MessageId",
                    CorrelationId = "CorrelationId",
                    ConnectionDeviceGenerationId = "ConnectionDeviceGenerationId",
                    ConnectionDeviceId           = "ConnectionDeviceId",
                    EnqueuedTime = "EnqueuedTime",
                },
            };
            string errorMessage;
            bool   isValid = validationService.IsValidFault(iconicsFault, out errorMessage);

            Assert.True(isValid);
        }
Beispiel #13
0
        public void ValidateIconicsFaultShouldReturnMessageIfInvalidAssetName()
        {
            ValidationService validationService = new ValidationService();
            IconicsFault      iconicsFault      = new IconicsFault()
            {
                FaultName = "FaultName",
                AssetPath = "AssetPath",
            };
            string errorMessage;
            bool   isValid         = validationService.IsValidFault(iconicsFault, out errorMessage);
            string expectedMessage = $"One or more required fields were not provided. Fault Name: {iconicsFault.FaultName}. Asset Name: {iconicsFault.AssetName}. Asset Path: {iconicsFault.AssetPath}.";

            Assert.Equal(expectedMessage, errorMessage);
            Assert.False(isValid);
        }
Beispiel #14
0
        /// <summary>
        /// Computes the SHA256 hash of ICONICS' FaultName + FaultActiveTime + AssetPath.
        /// </summary>
        /// <param name="iconicsFault">Fault Data.</param>
        /// <returns>return hash value as string.</returns>
        public static string ComputeSha256Hash(IconicsFault iconicsFault)
        {
            string hashValue = string.Empty;

            if (iconicsFault != null)
            {
                // create a hash of the ICONICS' FaultName + FaultActiveTime + AssetPath
                string alertToken = iconicsFault.FaultName + iconicsFault.FaultActiveTime + iconicsFault.AssetPath;
                using (var sha256 = new SHA256Managed())
                {
                    byte[] data = sha256.ComputeHash(Encoding.UTF8.GetBytes(alertToken));
                    hashValue = BitConverter.ToString(data).Replace("-", string.Empty, StringComparison.OrdinalIgnoreCase);
                }
            }

            return(hashValue);
        }
Beispiel #15
0
        public async void ShouldRunSuccessfullyOnValidActiveFault()
        {
            // mock IsValidFault and IsCdsServiceReady calls to return true
            string validationMessage;

            this.validationServiceMock.Setup(service => service.IsValidFault(It.IsAny <IconicsFault>(), out validationMessage)).Returns(true);
            this.dynamicsEntityServiceMock.Setup(service => service.IsCdsServiceReady()).Returns(true);

            var durableClientMock = new Mock <IDurableEntityClient>();

            // create message to pass to Run method
            IconicsFault iconicsFault = new IconicsFault()
            {
                FaultName            = "FaultName",
                AssetName            = "AssetName",
                AssetPath            = "AssetPath",
                FaultState           = "Active",
                FaultActiveTime      = "2020-08-13T20:01:04.6565528Z",
                EventEnqueuedUtcTime = DateTime.UtcNow.ToString("o", CultureInfo.CurrentCulture),
            };

            string  messageBody = JsonConvert.SerializeObject(iconicsFault);
            Message message     = new Message(Encoding.UTF8.GetBytes(messageBody));

            using FakeTelemetryChannel fakeTelemetryChannel = new FakeTelemetryChannel();

            using TelemetryConfiguration telemetryConfiguration = new TelemetryConfiguration(INSTRUMENTATIONID, fakeTelemetryChannel);

            // instantiate function class and run function
            CreateAlertFromIconicsFault function = new CreateAlertFromIconicsFault(telemetryConfiguration, this.dynamicsEntityServiceMock.Object, this.validationServiceMock.Object, this.errorQueueServiceMock.Object, this.instanceId, this.httpClientMock.Object);
            await function.Run(message, this.logger).ConfigureAwait(false);

            // verify that asset and alert are created
            string msg;

            this.validationServiceMock.Verify(x => x.IsValidFault(It.IsAny <IconicsFault>(), out msg), Times.Once);
            this.dynamicsEntityServiceMock.Verify(x => x.IsCdsServiceReady(), Times.Once);
            this.dynamicsEntityServiceMock.Verify(service => service.CreateAssetIfNotExists(It.IsAny <IconicsFault>(), It.IsAny <ILogger>()), Times.Once);
            this.dynamicsEntityServiceMock.Verify(service => service.CreateAlert(It.IsAny <IconicsFault>(), It.IsAny <Guid>(), It.IsAny <ILogger>()), Times.Once);

            var logMsg = this.logger.Logs[0];

            Assert.Equal(
                $"Received ICONICS fault with FaultName '{iconicsFault.FaultName}' and FaultActiveTime of '{iconicsFault.FaultActiveTime}' and AssetPath of '{iconicsFault.AssetPath}'.",
                logMsg);
        }
Beispiel #16
0
        public async void ShouldRunUnsuccessfullyOnInvalidFault()
        {
            // mock IsValidFault call to return false
            string validationMessage;

            this.validationServiceMock.Setup(service => service.IsValidFault(It.IsAny <IconicsFault>(), out validationMessage)).Returns(false);

            // create message to pass to Run method
            IconicsFault iconicsFault = new IconicsFault()
            {
                FaultName            = "FaultName",
                AssetName            = "AssetName",
                AssetPath            = "AssetPath",
                EventEnqueuedUtcTime = DateTime.UtcNow.ToString("o", CultureInfo.CurrentCulture),
            };

            string  messageBody = JsonConvert.SerializeObject(iconicsFault);
            Message message     = new Message(Encoding.UTF8.GetBytes(messageBody));

            var durableClientMock = new Mock <IDurableEntityClient>();

            // set actualMessageContents equal to the first parameter passed into the SendMessageToErrorQueue method which should be called within the Run method
            string actualMessageContents = null;

            this.errorQueueServiceMock.Setup(service => service.SendMessageToErrorQueueAsync(It.IsAny <string>(), It.IsAny <string>())).Callback <string, string>((messageContents, validationMessage) => actualMessageContents = messageContents);

            using FakeTelemetryChannel fakeTelemetryChannel = new FakeTelemetryChannel();

            using TelemetryConfiguration telemetryConfiguration = new TelemetryConfiguration(INSTRUMENTATIONID, fakeTelemetryChannel);

            // instantiate function class and run function
            CreateAlertFromIconicsFault function = new CreateAlertFromIconicsFault(telemetryConfiguration, this.dynamicsEntityServiceMock.Object, this.validationServiceMock.Object, this.errorQueueServiceMock.Object, this.instanceId, this.httpClientMock.Object);
            await function.Run(message, this.logger).ConfigureAwait(false);

            string msg;

            this.validationServiceMock.Verify(x => x.IsValidFault(It.IsAny <IconicsFault>(), out msg), Times.Once);

            // verify that SendMessageToErrorQueue was called and received the correct messageContents parameter
            string expectedErrorText = "Iconics fault data failed validation. Sending message to the error queue.";

            this.errorQueueServiceMock.Verify(service => service.SendMessageToErrorQueueAsync(It.IsAny <string>(), It.IsAny <string>()), Times.Once);
            Assert.Equal(Encoding.UTF8.GetString(message.Body), actualMessageContents);
            Assert.Equal(expectedErrorText, this.logger.Logs[1]);
        }
        public void UpdateIoTAlertShouldUpdateAlertInfo()
        {
            // mock RetrieveMultiple call to retrieve an entity with a known id
            Guid             alertId = Guid.NewGuid();
            EntityCollection queryExpressionResult = new EntityCollection();

            queryExpressionResult.Entities.Add(new Entity()
            {
                Id = alertId
            });
            this.cdsServiceClientMock.Setup(service => service.RetrieveMultiple(It.IsAny <QueryBase>())).Returns(queryExpressionResult);

            Guid updatedAlertid = Guid.Empty;

            this.cdsServiceClientMock.Setup(service => service.Update(It.IsAny <Entity>())).Callback <Entity>(entity => updatedAlertid = entity.Id);

            // create DynamicsEntityService to test
            DynamicsEntityService dynamicsEntityService = new DynamicsEntityService(this.cdsServiceClientMock.Object);

            IconicsFault iconicsFault = new IconicsFault()
            {
                FaultName       = "TestFault",
                AssetName       = "TestAsset",
                AssetPath       = "TestAssetPath",
                FaultState      = "InActive",
                FaultActiveTime = "2020-08-13T20:01:04.6565528Z",
            };

            // call UpdateIoTAlert
            dynamicsEntityService.UpdateIoTAlert(iconicsFault, this.loggerMock, 0, 0);

            this.cdsServiceClientMock.Verify(service => service.Update(It.IsAny <Entity>()), Times.Once);
            this.cdsServiceClientMock.Verify(service => service.RetrieveMultiple(It.IsAny <QueryBase>()), Times.Once);

            // verify that the correct id is passed into Update call
            Assert.Equal(alertId, updatedAlertid);
            string expectedupdatelog = "Updated State of IoT Alert to InActive and Alert Data with latest Fault Data";

            // Verify that the correct message is logged from UpdateIoTAlert
            Assert.Contains(expectedupdatelog, this.loggerMock.Logs[0], StringComparison.OrdinalIgnoreCase);
        }
Beispiel #18
0
        /// <summary>
        /// Method definition to check if Fault Data is valid. Returns true if valid.
        /// </summary>
        /// <param name="iconicsFault">Fault Data.</param>
        /// <param name="validationMessage"> Validation Message content.</param>
        /// <returns>Returns true or false indicating the status of the validation.</returns>
        public bool IsValidFault(IconicsFault iconicsFault, out string validationMessage)
        {
            bool isValid = true;

            validationMessage = string.Empty;
            if (iconicsFault != null)
            {
                if (string.IsNullOrEmpty(iconicsFault.FaultName) || string.IsNullOrEmpty(iconicsFault.AssetName) || string.IsNullOrEmpty(iconicsFault.AssetPath))
                {
                    validationMessage = $"One or more required fields were not provided. Fault Name: {iconicsFault.FaultName}. Asset Name: {iconicsFault.AssetName}. Asset Path: {iconicsFault.AssetPath}.";
                    isValid           = false;
                }
            }
            else
            {
                validationMessage = Resources.SBTopicMsgNull;
                isValid           = false;
            }

            return(isValid);
        }
Beispiel #19
0
        /// <summary>
        /// Update existing IoT Alert to set status to InActive and update latest fault data.
        /// </summary>
        /// <param name="fault">Fault Data.</param>
        /// <param name="log">Log Content.</param>
        /// <param name="alertState">IoT Alert State Code.</param>
        /// <param name="alertStatus">IoT Alert Status Code.</param>
        public void UpdateIoTAlert(IconicsFault fault, ILogger log, int alertState = 0, int alertStatus = 0)
        {
            if (fault != null)
            {
                Guid alertId = this.GetIoTAlert(fault, log);
                if (alertId != Guid.Empty)
                {
                    Entity updateIoTAlert = new Entity(DynamicsEntities.IoTAlert);
                    updateIoTAlert.Id = alertId;
                    updateIoTAlert[IoTAlertProperties.MsdynAlertData] = JsonConvert.SerializeObject(fault);
                    updateIoTAlert[IoTAlertProperties.StateCode]      = alertState;
                    updateIoTAlert[IoTAlertProperties.StatusCode]     = alertStatus;

                    GetExecutionPolicy(log).Execute(() =>
                    {
                        this.cdsServiceClient.Update(updateIoTAlert);
                    });

                    log.LogInformation($"Retrieved IoT Alert {alertId}. Updated State of IoT Alert to InActive and Alert Data with latest Fault Data");
                }
            }
        }
Beispiel #20
0
        public async void ShouldLogWarningIfEventEnqueuedTimeUnavailable()
        {
            // mock IsValidFault call to return true, but IsCdsServiceReady to return false
            string validationMessage;

            this.validationServiceMock.Setup(service => service.IsValidFault(It.IsAny <IconicsFault>(), out validationMessage)).Returns(true);
            this.dynamicsEntityServiceMock.Setup(service => service.IsCdsServiceReady()).Returns(true);

            var durableClientMock = new Mock <IDurableEntityClient>();

            // create message to pass to Run method
            IconicsFault iconicsFault = new IconicsFault()
            {
                FaultName       = "FaultName",
                FaultActiveTime = "2020-08-13T20:01:04.6565528Z",
                AssetName       = "AssetName",
                AssetPath       = "AssetPath",
            };

            string  messageBody = JsonConvert.SerializeObject(iconicsFault);
            Message message     = new Message(Encoding.UTF8.GetBytes(messageBody));

            using FakeTelemetryChannel fakeTelemetryChannel = new FakeTelemetryChannel();

            using TelemetryConfiguration telemetryConfiguration = new TelemetryConfiguration(INSTRUMENTATIONID, fakeTelemetryChannel);

            // instantiate function class and run function
            CreateAlertFromIconicsFault function = new CreateAlertFromIconicsFault(telemetryConfiguration, this.dynamicsEntityServiceMock.Object, this.validationServiceMock.Object, this.errorQueueServiceMock.Object, this.instanceId, this.httpClientMock.Object);

            await function.Run(message, this.logger).ConfigureAwait(false);

            string msg;

            this.validationServiceMock.Verify(x => x.IsValidFault(It.IsAny <IconicsFault>(), out msg), Times.Once);
            this.dynamicsEntityServiceMock.Verify(x => x.IsCdsServiceReady(), Times.Once);

            Assert.Empty(fakeTelemetryChannel.SentEventTelemetries);
            Assert.Equal("Unable to determine IoT alert created elapsed time due to missing fault event enqueued time.", this.logger.Logs[1]);
        }
        public void CreateAssetIfNotExistsShouldReturnNewIdWhenAssetDoesNotExist()
        {
            // mock RetrieveMultiple call to retrieve an empty entity collection
            EntityCollection queryExpressionResult = new EntityCollection();

            this.cdsServiceClientMock.Setup(service => service.RetrieveMultiple(It.IsAny <QueryBase>())).Returns(queryExpressionResult);

            // mock Create call to retrieve a known asset id
            Guid assetId = Guid.NewGuid();

            this.cdsServiceClientMock.Setup(service => service.Create(It.IsAny <Entity>())).Returns(assetId);

            // create DynamicsEntityService to test
            DynamicsEntityService dynamicsEntityService = new DynamicsEntityService(this.cdsServiceClientMock.Object);

            // call CreateAssetIfNotExists
            IconicsFault iconicsFault = new IconicsFault()
            {
                AssetName       = "TestAsset",
                FaultName       = "TestFaultName",
                FaultActiveTime = "2020-08-13T20:01:04.6565528Z",
                AssetPath       = "TestAssetPath",
            };

            Guid returnedId = dynamicsEntityService.CreateAssetIfNotExists(iconicsFault, this.loggerMock);

            this.cdsServiceClientMock.Verify(x => x.RetrieveMultiple(It.IsAny <QueryBase>()), Times.Once);
            this.cdsServiceClientMock.Verify(x => x.Create(It.IsAny <Entity>()), Times.Once);

            Assert.Equal(
                $"Created Asset {returnedId} from Fault (FaultName: {iconicsFault.FaultName}, FaultActiveTime: {iconicsFault.FaultActiveTime}, AssetName: {iconicsFault.AssetName}, AssetPath: {iconicsFault.AssetPath}).",
                this.loggerMock.Logs[0]);

            // verify that returned id is equal to the generated entity id
            Assert.Equal(assetId, returnedId);
        }
Beispiel #22
0
        /// <summary>
        /// Method definition for creating an Asset if not exist in Dynamics.
        /// </summary>
        /// <param name="iconicsFault">Fault Data.</param>
        /// <param name="log">Log Content.</param>
        /// <returns>Customer Asset record GUID.</returns>
        public Guid CreateAssetIfNotExists(IconicsFault iconicsFault, ILogger log)
        {
            Guid assetId = Guid.Empty;

            if (iconicsFault != null)
            {
                // build query expression that finds the asset with the name supplied in the fault
                QueryExpression queryExpression = new QueryExpression()
                {
                    Distinct   = false,
                    EntityName = DynamicsEntities.CustomerAsset,
                    ColumnSet  = new ColumnSet(CustomerAssetProperties.MsdynName),
                    Criteria   =
                    {
                        Filters            =
                        {
                            new FilterExpression
                            {
                                Conditions =
                                {
                                    new ConditionExpression(CustomerAssetProperties.MsdynName, ConditionOperator.Equal, iconicsFault.AssetName),
                                },
                            },
                        },
                    },
                };

                // run the query to determine if the asset already exists in Dynamics
                EntityCollection queryExpressionResult = GetExecutionPolicy(log).Execute(() =>
                {
                    return(this.cdsServiceClient.RetrieveMultiple(queryExpression));
                });

                if (queryExpressionResult.Entities.Count == 0)
                {
                    // asset does not exist, create a new one and return its id
                    Entity newAsset = new Entity(DynamicsEntities.CustomerAsset);
                    newAsset[CustomerAssetProperties.MsdynName]  = iconicsFault.AssetName;
                    newAsset[CustomerAssetProperties.StateCode]  = CustomerAssetStateCode.Active;
                    newAsset[CustomerAssetProperties.StatusCode] = CustomerAssetStatusCode.Active;

                    assetId = GetExecutionPolicy(log).Execute(() =>
                    {
                        return(this.cdsServiceClient.Create(newAsset));
                    });

                    log.LogInformation(
                        "Created Asset {assetId} from Fault (FaultName: {FaultName}, FaultActiveTime: {FaultActiveTime}, AssetName: {AssetName}, AssetPath: {AssetPath}).",
                        assetId,
                        iconicsFault.FaultName,
                        iconicsFault.FaultActiveTime,
                        iconicsFault.AssetName,
                        iconicsFault.AssetPath);

                    return(assetId);
                }
                else
                {
                    // asset does exist, return its id
                    assetId = queryExpressionResult.Entities[0].Id;
                    log.LogInformation("Retrieved Asset {assetId}.", assetId);

                    return(assetId);
                }
            }

            return(assetId);
        }
Beispiel #23
0
        public async Task Run(
            [ServiceBusTrigger("%ServiceBusTopic%", "%ServiceBusSubscription%", Connection = "ServiceBusConnection")] Message message,
            ILogger log)
        {
            IconicsFault iconicsFault    = null;
            string       messageContents = null;

            if (message == null)
            {
                // This should never happen.
                throw new ArgumentNullException(nameof(message));
            }

            try
            {
                if (message != null)
                {
                    // deserialize message contents into iconics fault class
                    messageContents = Encoding.UTF8.GetString(message.Body);
                    iconicsFault    = JsonConvert.DeserializeObject <IconicsFault>(messageContents);

                    log.LogInformation(
                        "Received ICONICS fault with FaultName '{faultName}' and FaultActiveTime of '{faultActiveTime}' and AssetPath of '{assetPath}'.",
                        iconicsFault.FaultName,
                        iconicsFault.FaultActiveTime,
                        iconicsFault.AssetPath);
                }
            }
            catch (JsonException e)
            {
                // message could not be deserialized, send to error queue
                log.LogError(
                    "Exception while processing message: {message}, exception :{exceptionMessage}. Sending message to the error queue.",
                    message,
                    e.Message);

                await this.errorQueueService.SendMessageToErrorQueueAsync(messageContents, e.Message).ConfigureAwait(false);

                return;
            }

            // validate fault data properties
            if (!this.validationService.IsValidFault(iconicsFault, out string validationMessage))
            {
                // send message to error queue
                log.LogError(Resources.FaultValidationFailure);
                await this.errorQueueService.SendMessageToErrorQueueAsync(messageContents, validationMessage).ConfigureAwait(false);
            }
            else
            {
                if (this.dynamicsEntityService.IsCdsServiceReady())
                {
                    // If Iconic Fault State is "Active", Function should create new IoT Alert in Dynamics
                    if (string.IsNullOrEmpty(iconicsFault.FaultState) || iconicsFault.FaultState.Equals("Active", StringComparison.OrdinalIgnoreCase))
                    {
                        // Create Dynamics asset (if necessary) and alert
                        Guid assetId = this.dynamicsEntityService.CreateAssetIfNotExists(iconicsFault, log);
                        Guid alertId = this.dynamicsEntityService.CreateAlert(iconicsFault, assetId, log);

                        // Determine the elapsed time to create the alert.
                        // Lack of an EventEnqueuedUtcTime should not prevent the alert from being created. The field impacts telemetry only.
                        var parseSucceeded = DateTime.TryParse(iconicsFault.EventEnqueuedUtcTime, out DateTime enqueuedDateTimeUtc);
                        if (!parseSucceeded)
                        {
                            log.LogWarning("Unable to determine IoT alert created elapsed time due to missing fault event enqueued time.");
                        }
                        else
                        {
                            var et = new EventTelemetry("IoTAlertCreated");
                            et.Metrics.Add("AlertCreatedElapsedTimeMs", (DateTime.UtcNow - enqueuedDateTimeUtc).TotalMilliseconds);
                            this.telemetryClient.TrackEvent(et);
                        }
                    }
                    else if (iconicsFault.FaultState.Equals("InActive", StringComparison.OrdinalIgnoreCase))
                    {
                        // If if Iconic Fault State is "InActive", Function should find existing IoT Alert and update Alert Data and Status of an IoT Alert to InActive in Dynamics

                        // Update IoT Alert State to InActive and Alert Data with latest fault data
                        this.dynamicsEntityService.UpdateIoTAlert(iconicsFault, log, (int)IotAlertStateCode.Inactive, (int)IotAlertStatusCode.Inactive);
                    }
                }
                else
                {
                    log.LogError("{msg}, error: {error}", Resources.CDSConnectionFailure, this.dynamicsEntityService.RetrieveCdsError());

                    FailureRequest failureReq = new FailureRequest
                    {
                        // RequestId = $"{message?.MessageId}:{Guid.NewGuid()}",
                        RequestId   = Guid.NewGuid().ToString(),
                        FailureTime = DateTime.UtcNow,
                        InstanceId  = this.instanceId.Id,
                        ResourceId  = ResourceId,
                    };

                    string appName = ResourceId.Split('/').Last();

                    string circuitBreakerBaseUri = $"{CircuitRequestUri}/{appName}/AddFailure";
                    var    response = await this.httpClient.PostAsJsonAsync($"{circuitBreakerBaseUri}?code={CircuitCode}", failureReq).ConfigureAwait(false);

                    if (!response.IsSuccessStatusCode)
                    {
                        log.LogError("Failed to invoke Circuit Breaker function, {circuitBreakerBaseUri}.  Status code: {code}.  Reason: '{reason}'", circuitBreakerBaseUri, response.StatusCode, response.ReasonPhrase);
                    }
                    else
                    {
                        log.LogInformation("Successfully invoked Circuit Breaker function, {circuitBreakerBaseUri}", circuitBreakerBaseUri);
                    }

                    // Throw an exception here in order to instruct the binding to Abandon the message (for the function to fail).
                    // See https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-service-bus-trigger?tabs=csharp#peeklock-behavior
                    throw new ApplicationException($"{Resources.CDSConnectionFailure}, error: {this.dynamicsEntityService.RetrieveCdsError()}");
                }
            }
        }