diff --git a/.github/workflows/monthly-copyright-update.yml b/.github/workflows/monthly-copyright-update.yml index a45e0138..152012af 100644 --- a/.github/workflows/monthly-copyright-update.yml +++ b/.github/workflows/monthly-copyright-update.yml @@ -12,5 +12,6 @@ jobs: uses: 51Degrees/common-ci/.github/workflows/monthly-copyright-update.yml@main with: repo-name: ${{ github.event.repository.name }} + org-name: ${{ github.event.repository.owner.login }} secrets: token: ${{ secrets.ACCESS_TOKEN }} diff --git a/.github/workflows/nightly-documentation-update.yml b/.github/workflows/nightly-documentation-update.yml index 775060f0..bb65c351 100644 --- a/.github/workflows/nightly-documentation-update.yml +++ b/.github/workflows/nightly-documentation-update.yml @@ -13,5 +13,6 @@ jobs: uses: 51Degrees/common-ci/.github/workflows/nightly-documentation-update.yml@main with: repo-name: ${{ github.event.repository.name }} + org-name: ${{ github.event.repository.owner.login }} secrets: token: ${{ secrets.ACCESS_TOKEN }} diff --git a/.github/workflows/nightly-package-update.yml b/.github/workflows/nightly-package-update.yml index 440ba369..d1642ad1 100644 --- a/.github/workflows/nightly-package-update.yml +++ b/.github/workflows/nightly-package-update.yml @@ -11,5 +11,6 @@ jobs: uses: 51Degrees/common-ci/.github/workflows/nightly-package-update.yml@main with: repo-name: ${{ github.event.repository.name }} + org-name: ${{ github.event.repository.owner.login }} secrets: token: ${{ secrets.ACCESS_TOKEN }} diff --git a/.github/workflows/nightly-prs-to-main.yml b/.github/workflows/nightly-prs-to-main.yml index 2ae4edd4..e02ac99f 100644 --- a/.github/workflows/nightly-prs-to-main.yml +++ b/.github/workflows/nightly-prs-to-main.yml @@ -12,5 +12,6 @@ jobs: uses: 51Degrees/common-ci/.github/workflows/nightly-prs-to-main.yml@main with: repo-name: ${{ github.event.repository.name }} + org-name: ${{ github.event.repository.owner.login }} secrets: token: ${{ secrets.ACCESS_TOKEN }} diff --git a/.github/workflows/nightly-publish-main.yml b/.github/workflows/nightly-publish-main.yml index 4f62866b..08ad172d 100644 --- a/.github/workflows/nightly-publish-main.yml +++ b/.github/workflows/nightly-publish-main.yml @@ -12,6 +12,7 @@ jobs: uses: 51Degrees/common-ci/.github/workflows/nightly-publish-main.yml@main with: repo-name: ${{ github.event.repository.name }} + org-name: ${{ github.event.repository.owner.login }} build-platform: windows-latest secrets: token: ${{ secrets.ACCESS_TOKEN }} diff --git a/.github/workflows/nightly-submodule-update.yml b/.github/workflows/nightly-submodule-update.yml index b2ec1118..f3c4bfb5 100644 --- a/.github/workflows/nightly-submodule-update.yml +++ b/.github/workflows/nightly-submodule-update.yml @@ -12,5 +12,6 @@ jobs: uses: 51Degrees/common-ci/.github/workflows/nightly-submodule-update.yml@main with: repo-name: ${{ github.event.repository.name }} + org-name: ${{ github.event.repository.owner.login }} secrets: token: ${{ secrets.ACCESS_TOKEN }} diff --git a/FiftyOne.Pipeline.Elements/FiftyOne.Pipeline.JavaScriptBuilderElement/FlowElement/JavaScriptBuilderElement.cs b/FiftyOne.Pipeline.Elements/FiftyOne.Pipeline.JavaScriptBuilderElement/FlowElement/JavaScriptBuilderElement.cs index d129f8b9..356ceb7f 100644 --- a/FiftyOne.Pipeline.Elements/FiftyOne.Pipeline.JavaScriptBuilderElement/FlowElement/JavaScriptBuilderElement.cs +++ b/FiftyOne.Pipeline.Elements/FiftyOne.Pipeline.JavaScriptBuilderElement/FlowElement/JavaScriptBuilderElement.cs @@ -245,12 +245,49 @@ protected override void ManagedResourcesCleanup() /// protected override void ProcessInternal(IFlowData data) { - if (data == null) throw new ArgumentNullException(nameof(data)); - SetUp(data); + SetUp(data, GetJSONFromData(data), GetOrAddToData(data), true); + } + + + /// + /// Default process method. + /// + /// + /// + /// + /// Thrown if the supplied flow data is null. + /// + public JavaScriptBuilderElementData GetFallbackResponse(IFlowData data, IJsonBuilderElementData jsonData) + { + if (jsonData == null) + { + throw new ArgumentNullException(nameof(jsonData)); + } + JavaScriptBuilderElementData result = (JavaScriptBuilderElementData)CreateElementData(data.Pipeline); + SetUp(data, () => jsonData, () => result, false); + return result; } - private void SetUp(IFlowData data) + private static Func GetJSONFromData(IFlowData data) => () => + { + try + { + return data.Get(); + } + catch (KeyNotFoundException ex) + { + throw new PipelineConfigurationException( + Messages.ExceptionJsonBuilderNotRun, ex); + } + }; + + private void SetUp( + IFlowData data, + Func jsonDataProvider, + Func targetElementDataProvider, + bool throwOnGetAsFailure) { + if (data == null) throw new ArgumentNullException(nameof(data)); var host = Host; var protocol = Protocol; bool supportsPromises = false; @@ -276,6 +313,8 @@ private void SetUp(IFlowData data) protocol = Constants.FALLBACK_PROTOCOL; } + const string errorFormat_GetAsFailed = "Failed to get property {propertyName}"; + // If device detection is in the Pipeline then we can check // if the client's browser supports promises. // This can be used to customize the JavaScript response. @@ -292,7 +331,19 @@ private void SetUp(IFlowData data) try { - var promise = data.GetAs>("Promise"); + IAspectPropertyValue promise = null; + try + { + promise = data.GetAs>("Promise"); + } + catch (Exception ex) + { + if (throwOnGetAsFailure) + { + throw; + } + Logger.LogError(ex, errorFormat_GetAsFailed, "Promise"); + } supportsPromises = promise != null && promise.HasValue && promise.Value == "Full"; } catch (PropertyMissingException) { promisesNotAvailable(); } @@ -317,7 +368,19 @@ private void SetUp(IFlowData data) try { - var fetch = data.GetAs>("Fetch"); + IAspectPropertyValue fetch = null; + try + { + fetch = data.GetAs>("Fetch"); + } + catch (Exception ex) + { + if (throwOnGetAsFailure) + { + throw; + } + Logger.LogError(ex, errorFormat_GetAsFailed, "Fetch"); + } supportsFetch = fetch != null && fetch.HasValue && fetch.Value; } catch (PropertyMissingException) { fetchNotAvailable(); } @@ -327,16 +390,8 @@ private void SetUp(IFlowData data) } // Get the JSON include to embed into the JavaScript include. - string jsonObject = string.Empty; - try - { - jsonObject = data.Get().Json; - } - catch (KeyNotFoundException ex) - { - throw new PipelineConfigurationException( - Messages.ExceptionJsonBuilderNotRun, ex); - } + string jsonObject = jsonDataProvider().Json; + var parameters = GetParameters(data); var paramsObject = JsonConvert.SerializeObject(parameters); @@ -380,7 +435,7 @@ private void SetUp(IFlowData data) } // With the gathered resources, build a new JavaScriptResource. - BuildJavaScript(data, jsonObject, sessionId, sequence, supportsPromises, supportsFetch, url, paramsObject); + BuildJavaScript(data, targetElementDataProvider, jsonObject, sessionId, sequence, supportsPromises, supportsFetch, url, paramsObject); } @@ -474,6 +529,12 @@ protected virtual string GetSessionId(IFlowData data) /// /// The instance to populate with the /// resulting + /// and additional evidence source + /// + /// + /// The method the will inject the resulting + /// + /// into the response (even if differs from `data` above) /// /// /// The JSON data object to include in the JavaScript. @@ -503,6 +564,7 @@ protected virtual string GetSessionId(IFlowData data) /// protected void BuildJavaScript( IFlowData data, + Func targetElementDataProvider, string jsonObject, string sessionId, int sequence, @@ -511,7 +573,15 @@ protected void BuildJavaScript( string url, string parameters) { - BuildJavaScript(data, jsonObject, sessionId, sequence, supportsPromises, supportsFetch, new Uri(url), parameters); + BuildJavaScript(data, targetElementDataProvider, jsonObject, sessionId, sequence, supportsPromises, supportsFetch, new Uri(url), parameters); + } + + private Func GetOrAddToData(IFlowData data) + { + return () => (JavaScriptBuilderElementData) + data.GetOrAdd( + ElementDataKeyTyped, + CreateElementData); } /// @@ -521,6 +591,12 @@ protected void BuildJavaScript( /// /// The instance to populate with the /// resulting + /// and additional evidence source + /// + /// + /// The method the will inject the resulting + /// + /// into the response (even if differs from `data` above) /// /// /// The JSON data object to include in the JavaScript. @@ -549,7 +625,8 @@ protected void BuildJavaScript( /// Thrown if the supplied flow data is null. /// protected void BuildJavaScript( - IFlowData data, + IFlowData data, + Func targetElementDataProvider, string jsonObject, string sessionId, int sequence, @@ -558,12 +635,9 @@ protected void BuildJavaScript( Uri url, string parameters) { - if (data == null) throw new ArgumentNullException(nameof(data)); - - JavaScriptBuilderElementData elementData = (JavaScriptBuilderElementData) - data.GetOrAdd( - ElementDataKeyTyped, - CreateElementData); + if (data == null) throw new ArgumentNullException(nameof(data)); + + JavaScriptBuilderElementData elementData = targetElementDataProvider(); string objectName = ObjName; // Try and get the requested object name from evidence. diff --git a/FiftyOne.Pipeline.Elements/FiftyOne.Pipeline.JavaScriptBuilderElementTests/JavaScriptBuilderElementTests.cs b/FiftyOne.Pipeline.Elements/FiftyOne.Pipeline.JavaScriptBuilderElementTests/JavaScriptBuilderElementTests.cs index 44d78872..66ede0b3 100644 --- a/FiftyOne.Pipeline.Elements/FiftyOne.Pipeline.JavaScriptBuilderElementTests/JavaScriptBuilderElementTests.cs +++ b/FiftyOne.Pipeline.Elements/FiftyOne.Pipeline.JavaScriptBuilderElementTests/JavaScriptBuilderElementTests.cs @@ -196,6 +196,34 @@ public void JavaScriptBuilder_VerifySession() $"JavaScript does not contain expected sequence '1'."); } + /// + /// Check that the callback URL is generated correctly. + /// + [TestMethod] + public void JavaScriptBuilder_VerifyFallbackResponse() + { + _javaScriptBuilderElement = + new JavaScriptBuilderElementBuilder(_loggerFactory) + .SetEndpoint("/json") + .Build(); + + var flowData = new Mock(); + + flowData.Setup(d => d.Get()) + .Throws(); + var evidence = new Evidence(_loggerFactory.CreateLogger()); + flowData.Setup(d => d.GetEvidence()) + .Returns(evidence); + + var jsonData = new Mock(); + jsonData.Setup(j => j.Json) + .Returns("{}"); + + IJavaScriptBuilderElementData result = _javaScriptBuilderElement.GetFallbackResponse(flowData.Object, jsonData.Object); + + Assert.IsFalse(string.IsNullOrWhiteSpace(result.JavaScript)); + } + /// /// Check that the callback URL is generated correctly. /// diff --git a/FiftyOne.Pipeline.Elements/FiftyOne.Pipeline.JsonBuilderElement/FlowElement/JsonBuilderElement.cs b/FiftyOne.Pipeline.Elements/FiftyOne.Pipeline.JsonBuilderElement/FlowElement/JsonBuilderElement.cs index fc34e3e0..a74f0ec5 100644 --- a/FiftyOne.Pipeline.Elements/FiftyOne.Pipeline.JsonBuilderElement/FlowElement/JsonBuilderElement.cs +++ b/FiftyOne.Pipeline.Elements/FiftyOne.Pipeline.JsonBuilderElement/FlowElement/JsonBuilderElement.cs @@ -215,7 +215,31 @@ protected override void ManagedResourcesCleanup() /// /// Thrown if the supplied flow data is null. /// - protected override void ProcessInternal(IFlowData data) + protected override void ProcessInternal(IFlowData data) => + BuildAndInjectJSON( + data, + data.GetOrAdd( + ElementDataKeyTyped, + CreateElementData), + true); + + /// + /// Transform the data in the flow data instance into a + /// JSON object. + /// + /// + /// The + /// + /// + /// The + /// + /// + /// Whether to throw if sequence number was not found + /// + /// + /// Thrown if the supplied flow data is null. + /// + private void BuildAndInjectJSON(IFlowData data, IJsonBuilderElementData elementData, bool requireSequenceNumber) { if (data == null) throw new ArgumentNullException(nameof(data)); @@ -225,24 +249,51 @@ protected override void ProcessInternal(IFlowData data) config = _pipelineConfigs.GetOrAdd(data.Pipeline, config); } - var elementData = data.GetOrAdd( - ElementDataKeyTyped, - CreateElementData); - var jsonString = BuildJson(data, config); + var jsonString = BuildJson(data, config, requireSequenceNumber); elementData.Json = jsonString; } + /// + /// Transform the data in the flow data instance into a + /// JSON object. + /// + /// + /// The + /// + /// + /// Thrown if the supplied flow data is null. + /// + public IJsonBuilderElementData GetFallbackResponse(IFlowData data) + { + var elementData = CreateElementData(data.Pipeline); + BuildAndInjectJSON(data, elementData, false); + return elementData; + } + /// /// Create and populate a JSON string from the specified data. /// /// /// The configuration to use + /// The configuration to use /// /// A string containing the data in JSON format. /// - protected virtual string BuildJson(IFlowData data, PipelineConfig config) + protected virtual string BuildJson(IFlowData data, PipelineConfig config, bool requireSequenceNumber) { - int sequenceNumber = GetSequenceNumber(data); + int? sequenceNumber = null; + try + { + sequenceNumber = GetSequenceNumber(data); + } + catch (PipelineException e) + { + if (requireSequenceNumber) + { + throw; + } + Logger.LogError(e, $"Failed to get {nameof(sequenceNumber)}."); + } // Get property values from all the elements and add the ones that // are accessible to a dictionary. @@ -250,7 +301,7 @@ protected virtual string BuildJson(IFlowData data, PipelineConfig config) // Only populate the JavaScript properties if the sequence // has not reached max iterations. - if (sequenceNumber < Constants.MAX_JAVASCRIPT_ITERATIONS) + if (!sequenceNumber.HasValue || sequenceNumber.Value < Constants.MAX_JAVASCRIPT_ITERATIONS) { AddJavaScriptProperties(data, allProperties); } diff --git a/FiftyOne.Pipeline.Elements/FiftyOne.Pipeline.JsonBuilderElementTests/JsonBuilderElementTests.cs b/FiftyOne.Pipeline.Elements/FiftyOne.Pipeline.JsonBuilderElementTests/JsonBuilderElementTests.cs index 8ba47e3d..58de5c52 100644 --- a/FiftyOne.Pipeline.Elements/FiftyOne.Pipeline.JsonBuilderElementTests/JsonBuilderElementTests.cs +++ b/FiftyOne.Pipeline.Elements/FiftyOne.Pipeline.JsonBuilderElementTests/JsonBuilderElementTests.cs @@ -98,6 +98,17 @@ public void JsonBuilder_ValidJson() Assert.IsTrue(IsExpectedJson(json)); } + /// + /// Check that the JSON produced by the JsonBuilder is valid. + /// + [TestMethod] + public void JsonBuilder_NonEmptyFallback() + { + var json = TestIteration(1, null, null, (e, fd) => ((JsonBuilderElement)e).GetFallbackResponse(fd).Json); + + Assert.IsFalse(string.IsNullOrWhiteSpace(json)); + } + /// /// Check that the JSON element removes JavaScript properties from the /// response after max number of iterations has been reached. @@ -731,7 +742,8 @@ public class JsonBuilder private string TestIteration(int iteration, Dictionary data = null, - Mock flowData = null) + Mock flowData = null, + Func processFunc = null) { if(data == null) { @@ -765,6 +777,11 @@ private string TestIteration(int iteration, }); flowData.Setup(d => d.Pipeline).Returns(_pipeline.Object); + if (processFunc != null) + { + return processFunc(_jsonBuilderElement, flowData.Object); + } + _jsonBuilderElement.Process(flowData.Object); var json = result["json"].ToString(); diff --git a/FiftyOne.Pipeline.Web.Shared/Services/ClientsidePropertyService.cs b/FiftyOne.Pipeline.Web.Shared/Services/ClientsidePropertyService.cs index 2e21407e..12911648 100644 --- a/FiftyOne.Pipeline.Web.Shared/Services/ClientsidePropertyService.cs +++ b/FiftyOne.Pipeline.Web.Shared/Services/ClientsidePropertyService.cs @@ -35,6 +35,8 @@ using System.Text; using FiftyOne.Pipeline.Web.Shared.Adapters; using Microsoft.Extensions.Logging; +using FiftyOne.Pipeline.JavaScriptBuilder.Data; +using FiftyOne.Pipeline.JsonBuilder.Data; namespace FiftyOne.Pipeline.Web.Shared.Services { @@ -48,15 +50,43 @@ public class ClientsidePropertyService : IClientsidePropertyService /// /// Pipeline /// - private IPipeline _pipeline; + private readonly IPipeline _pipeline; - private ILogger _logger; + private readonly ILogger _logger; /// /// A list of all the HTTP headers that are requested evidence /// for elements that populate JavaScript properties /// - private StringValues _headersAffectingJavaScript; + private StringValues? _headersAffectingJavaScript = null; + private readonly object _headersAffectingJavaScriptLock = new object(); + + private StringValues HeadersAffectingJavaScript + { + get + { + if (_headersAffectingJavaScript.HasValue) + { + return _headersAffectingJavaScript.Value; + } + + lock (_headersAffectingJavaScriptLock) + { + if (_headersAffectingJavaScript.HasValue) + { + return _headersAffectingJavaScript.Value; + } + + CollectHeadersAffectingJavaScript(out StringValues newHeaders, out bool gotExceptions); + + if (!gotExceptions) + { + _headersAffectingJavaScript = newHeaders; + } + return newHeaders; + } + } + } private enum ContentType { @@ -68,7 +98,7 @@ private enum ContentType /// The cache control values that will be set for the JavaScript and /// JSON. /// - private StringValues _cacheControl = new StringValues( + private readonly StringValues _cacheControl = new StringValues( new string[] { "private", "max-age=1800", @@ -90,11 +120,27 @@ public ClientsidePropertyService( _pipeline = pipeline; _logger = logger; + } - var headersAffectingJavaScript = new List(); + private void CollectHeadersAffectingJavaScript(out StringValues headers, out bool gotExceptions) + { + var headersAffectingJavaScript = new List(); + gotExceptions = false; + + foreach (var flowElement in _pipeline.FlowElements) + { + IEvidenceKeyFilter filter; + try + { + filter = flowElement.EvidenceKeyFilter; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Failed to get {nameof(flowElement.EvidenceKeyFilter)} from {{flowElementType}}", flowElement.GetType().Name); + gotExceptions = true; + continue; + } - foreach (var filter in _pipeline.FlowElements.Select(e => e.EvidenceKeyFilter)) - { // If the filter is a white list or derived type then // get all HTTP header evidence keys from white list // and add them to the headers that could affect the @@ -120,7 +166,7 @@ public ClientsidePropertyService( .Distinct(StringComparer.OrdinalIgnoreCase)); } } - _headersAffectingJavaScript = new StringValues(headersAffectingJavaScript.ToArray()); + headers = new StringValues(headersAffectingJavaScript.ToArray()); } /// @@ -167,10 +213,27 @@ private void ServeContent(IContextAdapter context, IFlowData flowData, ContentTy context.Response.Clear(); context.Response.ClearHeaders(); - // Get the hash code. - var hash = flowData.GenerateKey(_pipeline.EvidenceKeyFilter).GetHashCode(); + bool hadFailures = false; + + IEvidenceKeyFilter pipelineEvidenceKeyFilter = null; + try + { + pipelineEvidenceKeyFilter = _pipeline.EvidenceKeyFilter; + } + catch (PipelineException ex) + { + _logger?.LogError(ex, $"Failed to get {nameof(_pipeline.EvidenceKeyFilter)} from {nameof(_pipeline)}"); + hadFailures = true; + } + + // Get the hash code. + int? hash = null; + if (pipelineEvidenceKeyFilter != null) { + hash = flowData.GenerateKey(pipelineEvidenceKeyFilter).GetHashCode(); + } - if (int.TryParse(context.Request.GetHeaderValue("If-None-Match"), + if (hash.HasValue && + int.TryParse(context.Request.GetHeaderValue("If-None-Match"), out int previousHash) && hash == previousHash) { @@ -181,27 +244,57 @@ private void ServeContent(IContextAdapter context, IFlowData flowData, ContentTy { // Otherwise, return the requested content to the client. string content = null; + Func GetJsonData = () => + { + var jsonElement = flowData.Pipeline.GetElement(); + if (jsonElement == null) + { + throw new PipelineConfigurationException( + Messages.ExceptionNoJsonBuilder); + } + IJsonBuilderElementData jsonData = null; + try + { + jsonData = flowData.GetFromElement(jsonElement); + } + catch (PipelineException ex) + { + _logger?.LogError(ex, "Failed to get data from {flowElementType}", jsonElement.GetType().Name); + jsonData = jsonElement.GetFallbackResponse(flowData); + hadFailures = true; + } + return jsonData; + }; + + Func GetJsData = () => + { + var jsElement = flowData.Pipeline.GetElement(); + if (jsElement == null) + { + throw new PipelineConfigurationException( + Messages.ExceptionNoJavaScriptBuilder); + } + IJavaScriptBuilderElementData jsData; + try + { + jsData = flowData.GetFromElement(jsElement); + } + catch (PipelineException ex) + { + _logger?.LogError(ex, "Failed to get data from {flowElementType}", jsElement.GetType().Name); + jsData = jsElement.GetFallbackResponse(flowData, GetJsonData()); + hadFailures = true; + } + return jsData; + }; + switch (contentType) { case ContentType.JavaScript: - var jsElement = flowData.Pipeline.GetElement(); - if (jsElement == null) - { - throw new PipelineConfigurationException( - Messages.ExceptionNoJavaScriptBuilder); - } - var jsData = flowData.GetFromElement(jsElement); - content = jsData?.JavaScript; + content = GetJsData()?.JavaScript; break; case ContentType.Json: - var jsonElement = flowData.Pipeline.GetElement(); - if (jsonElement == null) - { - throw new PipelineConfigurationException( - Messages.ExceptionNoJsonBuilder); - } - var jsonData = flowData.GetFromElement(jsonElement); - content = jsonData?.Json; + content = GetJsonData()?.Json; break; default: break; @@ -215,9 +308,10 @@ private void ServeContent(IContextAdapter context, IFlowData flowData, ContentTy context.Response.StatusCode = 200; SetHeaders(context, - hash.ToString(CultureInfo.InvariantCulture), + hash.HasValue ? hash.Value.ToString(CultureInfo.InvariantCulture) : null, length, - contentType == ContentType.JavaScript ? "x-javascript" : "json"); + contentType == ContentType.JavaScript ? "x-javascript" : "json", + !hadFailures); context.Response.Write(content); } @@ -231,7 +325,8 @@ private void ServeContent(IContextAdapter context, IFlowData flowData, ContentTy /// /// /// - private void SetHeaders(IContextAdapter context, string hash, int contentLength, string contentType) + /// + private void SetHeaders(IContextAdapter context, string hash, int contentLength, string contentType, bool shouldCache) { try { @@ -239,15 +334,19 @@ private void SetHeaders(IContextAdapter context, string hash, int contentLength, $"application/{contentType}"); context.Response.SetHeader("Content-Length", contentLength.ToString(CultureInfo.InvariantCulture)); - context.Response.SetHeader("Cache-Control", _cacheControl); - if (string.IsNullOrEmpty(_headersAffectingJavaScript.ToString()) == false) + context.Response.SetHeader("Cache-Control", shouldCache ? _cacheControl.ToString() : "no-cache"); + var headersAffectingJavaScript = HeadersAffectingJavaScript; + if (string.IsNullOrEmpty(headersAffectingJavaScript.ToString()) == false) { - context.Response.SetHeader("Vary", _headersAffectingJavaScript); + context.Response.SetHeader("Vary", headersAffectingJavaScript); + } + if (!string.IsNullOrEmpty(hash)) + { + context.Response.SetHeader("ETag", new StringValues( + new string[] { + hash, + })); } - context.Response.SetHeader("ETag", new StringValues( - new string[] { - hash, - })); var origin = GetAllowOrigin(context.Request); if (origin != null) { diff --git a/Web Integration/Tests/FiftyOne.Pipeline.Web.Shared.Tests/ClientsidePropertyServiceTests.cs b/Web Integration/Tests/FiftyOne.Pipeline.Web.Shared.Tests/ClientsidePropertyServiceTests.cs index 05e8bba4..f15d1118 100644 --- a/Web Integration/Tests/FiftyOne.Pipeline.Web.Shared.Tests/ClientsidePropertyServiceTests.cs +++ b/Web Integration/Tests/FiftyOne.Pipeline.Web.Shared.Tests/ClientsidePropertyServiceTests.cs @@ -437,6 +437,8 @@ private void Configure(List flowElements = null) } _pipeline.Setup(p => p.FlowElements) .Returns(flowElements); + _pipeline.Setup(p => p.EvidenceKeyFilter) + .Returns(new EvidenceKeyFilterWhitelist(new List())); // Configure the key for this flow data to contain a fake value // that we can use to test the cached response handling. _defaultDataKey = new DataKeyBuilder().Add(1, "test", "value").Build();