From f0cfdd63227bdae10a592e99eedfba19e2975ba5 Mon Sep 17 00:00:00 2001 From: clemensv Date: Tue, 27 Nov 2018 17:15:52 +0100 Subject: [PATCH] Initial extensions test coverage. Completed extensions Signed-off-by: clemensv --- src/CloudNative.CloudEvents/CloudEvent.cs | 3 + .../CloudEventAttributes.cs | 5 +- .../CloudEventContent.cs | 10 + src/CloudNative.CloudEvents/ContentMode.cs | 8 +- .../Extensions/DistributedTracingExtension.cs | 10 +- .../Extensions/IntegerSequenceExtension.cs | 37 ++++ .../Extensions/SamplingExtension.cs | 84 ++++++++ .../Extensions/SequenceExtension.cs | 86 ++++++++ .../HttpClientExtension.cs | 52 +++++ .../ICloudEventExtension.cs | 3 + .../ICloudEventFormatter.cs | 35 ++++ .../JsonEventFormatter.cs | 3 + .../Strings.Designer.cs | 27 +++ src/CloudNative.CloudEvents/Strings.resx | 9 + .../ExtensionsTest.cs | 189 ++++++++++++++++++ 15 files changed, 554 insertions(+), 7 deletions(-) create mode 100644 src/CloudNative.CloudEvents/Extensions/IntegerSequenceExtension.cs create mode 100644 src/CloudNative.CloudEvents/Extensions/SamplingExtension.cs create mode 100644 src/CloudNative.CloudEvents/Extensions/SequenceExtension.cs create mode 100644 test/CloudNative.CloudEvents.UnitTests/ExtensionsTest.cs diff --git a/src/CloudNative.CloudEvents/CloudEvent.cs b/src/CloudNative.CloudEvents/CloudEvent.cs index 831bfa9..d21978f 100644 --- a/src/CloudNative.CloudEvents/CloudEvent.cs +++ b/src/CloudNative.CloudEvents/CloudEvent.cs @@ -8,6 +8,9 @@ namespace CloudNative.CloudEvents using System.Collections.Generic; using System.Net.Mime; + /// + /// Represents a CloudEvent + /// public class CloudEvent { public const string MediaType = "application/cloudevents"; diff --git a/src/CloudNative.CloudEvents/CloudEventAttributes.cs b/src/CloudNative.CloudEvents/CloudEventAttributes.cs index 769d373..8073d32 100644 --- a/src/CloudNative.CloudEvents/CloudEventAttributes.cs +++ b/src/CloudNative.CloudEvents/CloudEventAttributes.cs @@ -10,6 +10,9 @@ namespace CloudNative.CloudEvents using System.Globalization; using System.Net.Mime; + /// + /// The CloudEvents attributes + /// public class CloudEventAttributes : IDictionary { public const string ContentTypeAttributeName = "contenttype"; @@ -32,7 +35,7 @@ namespace CloudNative.CloudEvents IEnumerable extensions; - public CloudEventAttributes(IEnumerable extensions) + internal CloudEventAttributes(IEnumerable extensions) { this.extensions = extensions; } diff --git a/src/CloudNative.CloudEvents/CloudEventContent.cs b/src/CloudNative.CloudEvents/CloudEventContent.cs index 06b71c6..45d06cc 100644 --- a/src/CloudNative.CloudEvents/CloudEventContent.cs +++ b/src/CloudNative.CloudEvents/CloudEventContent.cs @@ -12,11 +12,21 @@ namespace CloudNative.CloudEvents using System.Text; using System.Threading.Tasks; + /// + /// This class is for use with `HttpClient` and constructs content and headers for + /// a HTTP request from a CloudEvent. + /// public class CloudEventContent : HttpContent { IInnerContent inner; static JsonEventFormatter jsonFormatter = new JsonEventFormatter(); + /// + /// Constructor + /// + /// CloudEvent + /// Content mode. Structured or binary. + /// Event formatter public CloudEventContent(CloudEvent cloudEvent, ContentMode contentMode, ICloudEventFormatter formatter) { if (contentMode == ContentMode.Structured) diff --git a/src/CloudNative.CloudEvents/ContentMode.cs b/src/CloudNative.CloudEvents/ContentMode.cs index e9c3919..85bdb2d 100644 --- a/src/CloudNative.CloudEvents/ContentMode.cs +++ b/src/CloudNative.CloudEvents/ContentMode.cs @@ -9,9 +9,13 @@ namespace CloudNative.CloudEvents /// public enum ContentMode { - // Structured mode. The complete CloudEvent is contained in the transport body + /// + /// Structured mode. The complete CloudEvent is contained in the transport body + /// Structured, - // Binary mode. The CloudEvent is projected onto the transport frame + /// + /// Binary mode. The CloudEvent is projected onto the transport frame + /// Binary } } \ No newline at end of file diff --git a/src/CloudNative.CloudEvents/Extensions/DistributedTracingExtension.cs b/src/CloudNative.CloudEvents/Extensions/DistributedTracingExtension.cs index fb30435..a46d499 100644 --- a/src/CloudNative.CloudEvents/Extensions/DistributedTracingExtension.cs +++ b/src/CloudNative.CloudEvents/Extensions/DistributedTracingExtension.cs @@ -15,7 +15,7 @@ namespace CloudNative.CloudEvents.Extensions IDictionary attributes = new Dictionary(); - public DistributedTracingExtension(string traceParent) + public DistributedTracingExtension(string traceParent = null) { this.TraceParent = traceParent; } @@ -43,9 +43,11 @@ namespace CloudNative.CloudEvents.Extensions foreach (var attr in attributes) { - eventAttributes[attr.Key] = attr.Value; - } - + if (attr.Value != null) + { + eventAttributes[attr.Key] = attr.Value; + } + } attributes = eventAttributes; } diff --git a/src/CloudNative.CloudEvents/Extensions/IntegerSequenceExtension.cs b/src/CloudNative.CloudEvents/Extensions/IntegerSequenceExtension.cs new file mode 100644 index 0000000..93dd393 --- /dev/null +++ b/src/CloudNative.CloudEvents/Extensions/IntegerSequenceExtension.cs @@ -0,0 +1,37 @@ +// Copyright (c) Cloud Native Foundation. +// Licensed under the Apache 2.0 license. +// See LICENSE file in the project root for full license information. + +namespace CloudNative.CloudEvents.Extensions +{ + public class IntegerSequenceExtension : SequenceExtension + { + public IntegerSequenceExtension(int? sequenceValue = null) : base() + { + base.SequenceType = "Integer"; + this.Sequence = sequenceValue; + } + + public new int? Sequence + { + get + { + var s = base.Sequence; + if (s != null) + { + return int.Parse(s); + } + return null; + } + set + { + base.Sequence = value?.ToString(); + } + } + + public new string SequenceType + { + get => base.SequenceType; + } + } +} \ No newline at end of file diff --git a/src/CloudNative.CloudEvents/Extensions/SamplingExtension.cs b/src/CloudNative.CloudEvents/Extensions/SamplingExtension.cs new file mode 100644 index 0000000..8d19ffa --- /dev/null +++ b/src/CloudNative.CloudEvents/Extensions/SamplingExtension.cs @@ -0,0 +1,84 @@ +// Copyright (c) Cloud Native Foundation. +// Licensed under the Apache 2.0 license. +// See LICENSE file in the project root for full license information. + +namespace CloudNative.CloudEvents.Extensions +{ + using System; + using System.Collections.Generic; + + public class SamplingExtension : ICloudEventExtension + { + public const string SampledRateAttributeName = "sampledrate"; + + IDictionary attributes = new Dictionary(); + + public SamplingExtension(int sampledRate = 0) + { + this.SampledRate = sampledRate; + } + + public int? SampledRate + { + get => (int?)this.attributes[SampledRateAttributeName]; + set => attributes[SampledRateAttributeName] = value; + } + + public Type GetAttributeType(string name) + { + switch (name) + { + case SampledRateAttributeName: + return typeof(int?); + } + + return null; + } + + void ICloudEventExtension.Attach(CloudEvent cloudEvent) + { + var eventAttributes = cloudEvent.GetAttributes(); + if (attributes == eventAttributes) + { + // already done + return; + } + + foreach (var attr in attributes) + { + eventAttributes[attr.Key] = attr.Value; + } + + attributes = eventAttributes; + } + + bool ICloudEventExtension.ValidateAndNormalize(string key, ref object value) + { + switch (key) + { + case SampledRateAttributeName: + if (value == null) + { + return true; + } + else if (value is string) + { + if (int.TryParse((string)value, out var i)) + { + value = (int?)i; + return true; + } + } + else if (value is int) + { + value = (int?)value; + return true; + } + + throw new InvalidOperationException(Strings.ErrorSampledRateValueIsaNotAnInteger); + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/CloudNative.CloudEvents/Extensions/SequenceExtension.cs b/src/CloudNative.CloudEvents/Extensions/SequenceExtension.cs new file mode 100644 index 0000000..eed5427 --- /dev/null +++ b/src/CloudNative.CloudEvents/Extensions/SequenceExtension.cs @@ -0,0 +1,86 @@ +// Copyright (c) Cloud Native Foundation. +// Licensed under the Apache 2.0 license. +// See LICENSE file in the project root for full license information. + +namespace CloudNative.CloudEvents.Extensions +{ + using System; + using System.Collections.Generic; + + public class SequenceExtension : ICloudEventExtension + { + public const string SequenceAttributeName = "sequence"; + + public const string SequenceTypeAttributeName = "sequencetype"; + + IDictionary attributes = new Dictionary(); + + public SequenceExtension() + { + } + + public string Sequence + { + get => attributes[SequenceAttributeName] as string; + set => attributes[SequenceAttributeName] = value; + } + + public string SequenceType + { + get => attributes[SequenceTypeAttributeName] as string; + set => attributes[SequenceTypeAttributeName] = value; + } + + public Type GetAttributeType(string name) + { + switch (name) + { + case SequenceAttributeName: + case SequenceTypeAttributeName: + return typeof(string); + } + + return null; + } + + void ICloudEventExtension.Attach(CloudEvent cloudEvent) + { + var eventAttributes = cloudEvent.GetAttributes(); + if (attributes == eventAttributes) + { + // already done + return; + } + + foreach (var attr in attributes) + { + eventAttributes[attr.Key] = attr.Value; + } + + attributes = eventAttributes; + } + + bool ICloudEventExtension.ValidateAndNormalize(string key, ref object value) + { + switch (key) + { + case SequenceAttributeName: + if (value == null || value is string) + { + return true; + } + + throw new InvalidOperationException(Strings.ErrorSequenceValueIsaNotAString); + case SequenceTypeAttributeName: + if (value == null || value is string) + { + return true; + } + + throw new InvalidOperationException(Strings.ErrorSequenceTypeValueIsaNotAString); + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/CloudNative.CloudEvents/HttpClientExtension.cs b/src/CloudNative.CloudEvents/HttpClientExtension.cs index 18bb168..066f94d 100644 --- a/src/CloudNative.CloudEvents/HttpClientExtension.cs +++ b/src/CloudNative.CloudEvents/HttpClientExtension.cs @@ -21,6 +21,14 @@ namespace CloudNative.CloudEvents const string SpecVersionHttpHeader = HttpHeaderPrefix + "specversion"; static JsonEventFormatter jsonFormatter = new JsonEventFormatter(); + /// + /// Copies the CloudEvent into this HttpListenerResponse instance + /// + /// this + /// CloudEvent to copy + /// Content mode (structured or binary) + /// Formatter + /// Task public static Task CopyFromAsync(this HttpListenerResponse httpListenerResponse, CloudEvent cloudEvent, ContentMode contentMode, ICloudEventFormatter formatter) { @@ -39,6 +47,14 @@ namespace CloudNative.CloudEvents return stream.CopyToAsync(httpListenerResponse.OutputStream); } + /// + /// Copies the CloudEvent into this HttpWebRequest instance + /// + /// this + /// CloudEvent to copy + /// Content mode (structured or binary) + /// Formatter + /// Task public static async Task CopyFromAsync(this HttpWebRequest httpWebRequest, CloudEvent cloudEvent, ContentMode contentMode, ICloudEventFormatter formatter) { @@ -58,6 +74,11 @@ namespace CloudNative.CloudEvents await stream.CopyToAsync(httpWebRequest.GetRequestStream()); } + /// + /// Indicates whether this HttpResponseMessage holds a CloudEvent + /// + /// + /// true, if the response is a CloudEvent public static bool IsCloudEvent(this HttpResponseMessage httpResponseMessage) { return ((httpResponseMessage.Content.Headers.ContentType != null && @@ -65,6 +86,9 @@ namespace CloudNative.CloudEvents httpResponseMessage.Headers.Contains(SpecVersionHttpHeader)); } + /// + /// Indicates whether this HttpListenerRequest holds a CloudEvent + /// public static bool IsCloudEvent(this HttpListenerRequest httpListenerRequest) { return ((httpListenerRequest.Headers["content-type"] != null && @@ -72,24 +96,52 @@ namespace CloudNative.CloudEvents httpListenerRequest.Headers.AllKeys.Contains(SpecVersionHttpHeader)); } + /// + /// Converts this response message into a CloudEvent object, with the given extensions. + /// + /// Response message + /// List of extension instances + /// A CloudEvent instance or 'null' if the response message doesn't hold a CloudEvent public static CloudEvent ToCloudEvent(this HttpResponseMessage httpResponseMessage, params ICloudEventExtension[] extensions) { return ToCloudEventInternal(httpResponseMessage, null, extensions); } + /// + /// Converts this response message into a CloudEvent object, with the given extensions and + /// overriding the default formatter. + /// + /// Response message + /// + /// List of extension instances + /// A CloudEvent instance or 'null' if the response message doesn't hold a CloudEvent public static CloudEvent ToCloudEvent(this HttpResponseMessage httpResponseMessage, ICloudEventFormatter formatter, params ICloudEventExtension[] extensions) { return ToCloudEventInternal(httpResponseMessage, formatter, extensions); } + /// + /// Converts this listener request into a CloudEvent object, with the given extensions. + /// + /// Listener request + /// List of extension instances + /// A CloudEvent instance or 'null' if the response message doesn't hold a CloudEvent public static CloudEvent ToCloudEvent(this HttpListenerRequest httpListenerRequest, params ICloudEventExtension[] extensions) { return ToCloudEvent(httpListenerRequest, null, extensions); } + /// + /// Converts this listener request into a CloudEvent object, with the given extensions, + /// overriding the formatter. + /// + /// Listener request + /// + /// List of extension instances + /// A CloudEvent instance or 'null' if the response message doesn't hold a CloudEvent public static CloudEvent ToCloudEvent(this HttpListenerRequest httpListenerRequest, ICloudEventFormatter formatter = null, params ICloudEventExtension[] extensions) diff --git a/src/CloudNative.CloudEvents/ICloudEventExtension.cs b/src/CloudNative.CloudEvents/ICloudEventExtension.cs index e42cd1e..ad5cb21 100644 --- a/src/CloudNative.CloudEvents/ICloudEventExtension.cs +++ b/src/CloudNative.CloudEvents/ICloudEventExtension.cs @@ -6,6 +6,9 @@ namespace CloudNative.CloudEvents { using System; + /// + /// Implemented for extension objects that reflect CloudEvent extension specifications. + /// public interface ICloudEventExtension { /// diff --git a/src/CloudNative.CloudEvents/ICloudEventFormatter.cs b/src/CloudNative.CloudEvents/ICloudEventFormatter.cs index 24a9dcf..bd429b0 100644 --- a/src/CloudNative.CloudEvents/ICloudEventFormatter.cs +++ b/src/CloudNative.CloudEvents/ICloudEventFormatter.cs @@ -9,12 +9,47 @@ namespace CloudNative.CloudEvents using System.Net.Mime; + /// + /// Implemented by formatters + /// public interface ICloudEventFormatter { + /// + /// Decode a structured event from a stream + /// + /// + /// + /// CloudEvent DecodeStructuredEvent(Stream data, IEnumerable extensions); + /// + /// Decode a structured event from a byte array + /// + /// + /// + /// CloudEvent DecodeStructuredEvent(byte[] data, IEnumerable extensions); + /// + /// Encode an structured event into a byte array + /// + /// + /// + /// byte[] EncodeStructuredEvent(CloudEvent cloudEvent, out ContentType contentType); + /// + /// Decode an attribute from a byte array + /// + /// + /// + /// + /// object DecodeAttribute(string name, byte[] data, IEnumerable extensions); + /// + /// Encode an attribute into a byte array + /// + /// + /// + /// + /// byte[] EncodeAttribute(string name, object value, IEnumerable extensions); } } \ No newline at end of file diff --git a/src/CloudNative.CloudEvents/JsonEventFormatter.cs b/src/CloudNative.CloudEvents/JsonEventFormatter.cs index 169297b..500fae5 100644 --- a/src/CloudNative.CloudEvents/JsonEventFormatter.cs +++ b/src/CloudNative.CloudEvents/JsonEventFormatter.cs @@ -12,6 +12,9 @@ namespace CloudNative.CloudEvents using Newtonsoft.Json; using Newtonsoft.Json.Linq; + /// + /// Formatter that implements the JSON Event Format + /// public class JsonEventFormatter : ICloudEventFormatter { diff --git a/src/CloudNative.CloudEvents/Strings.Designer.cs b/src/CloudNative.CloudEvents/Strings.Designer.cs index 0dcc67e..5e9976c 100644 --- a/src/CloudNative.CloudEvents/Strings.Designer.cs +++ b/src/CloudNative.CloudEvents/Strings.Designer.cs @@ -78,6 +78,15 @@ namespace CloudNative.CloudEvents { } } + /// + /// Looks up a localized string similar to The 'sampledrate' attribute value must be an integer. + /// + internal static string ErrorSampledRateValueIsaNotAnInteger { + get { + return ResourceManager.GetString("ErrorSampledRateValueIsaNotAnInteger", resourceCulture); + } + } + /// /// Looks up a localized string similar to The 'schemaurl' attribute value must be a valid absolute or relative URI. /// @@ -87,6 +96,24 @@ namespace CloudNative.CloudEvents { } } + /// + /// Looks up a localized string similar to The 'sequencetype' attribute value must be an integer. + /// + internal static string ErrorSequenceTypeValueIsaNotAString { + get { + return ResourceManager.GetString("ErrorSequenceTypeValueIsaNotAString", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The 'sequence' attribute value must be an integer. + /// + internal static string ErrorSequenceValueIsaNotAString { + get { + return ResourceManager.GetString("ErrorSequenceValueIsaNotAString", resourceCulture); + } + } + /// /// Looks up a localized string similar to The 'source' attribute value must be a valid absolute or relative URI. /// diff --git a/src/CloudNative.CloudEvents/Strings.resx b/src/CloudNative.CloudEvents/Strings.resx index 582c460..7b4f758 100644 --- a/src/CloudNative.CloudEvents/Strings.resx +++ b/src/CloudNative.CloudEvents/Strings.resx @@ -123,9 +123,18 @@ The 'id' attribute value must be a string + + The 'sampledrate' attribute value must be an integer + The 'schemaurl' attribute value must be a valid absolute or relative URI + + The 'sequencetype' attribute value must be an integer + + + The 'sequence' attribute value must be an integer + The 'source' attribute value must be a valid absolute or relative URI diff --git a/test/CloudNative.CloudEvents.UnitTests/ExtensionsTest.cs b/test/CloudNative.CloudEvents.UnitTests/ExtensionsTest.cs new file mode 100644 index 0000000..58fb15e --- /dev/null +++ b/test/CloudNative.CloudEvents.UnitTests/ExtensionsTest.cs @@ -0,0 +1,189 @@ +// Copyright (c) Cloud Native Foundation. +// Licensed under the Apache 2.0 license. +// See LICENSE file in the project root for full license information. + +namespace CloudNative.CloudEvents.UnitTests +{ + using System; + using System.Net.Mime; + using System.Text; + using CloudNative.CloudEvents.Extensions; + using Xunit; + + public class ExtensionsTest + { + const string jsonDistTrace = + "{\n" + + " \"specversion\" : \"0.2\",\n" + + " \"type\" : \"com.github.pull.create\",\n" + + " \"source\" : \"https://github.com/cloudevents/spec/pull/123\",\n" + + " \"id\" : \"A234-1234-1234\",\n" + + " \"time\" : \"2018-04-05T17:31:00Z\",\n" + + " \"traceparent\" : \"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01\",\n" + + " \"tracestate\" : \"rojo=00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01,congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4=\",\n" + + " \"contenttype\" : \"text/plain\",\n" + + " \"data\" : \"test\"\n" + + "}"; + + const string jsonSequence = + "{\n" + + " \"specversion\" : \"0.2\",\n" + + " \"type\" : \"com.github.pull.create\",\n" + + " \"source\" : \"https://github.com/cloudevents/spec/pull/123\",\n" + + " \"id\" : \"A234-1234-1234\",\n" + + " \"time\" : \"2018-04-05T17:31:00Z\",\n" + + " \"sequencetype\" : \"Integer\",\n" + + " \"sequence\" : \"25\",\n" + + " \"contenttype\" : \"text/plain\",\n" + + " \"data\" : \"test\"\n" + + "}"; + + const string jsonSampledRate = + "{\n" + + " \"specversion\" : \"0.2\",\n" + + " \"type\" : \"com.github.pull.create\",\n" + + " \"source\" : \"https://github.com/cloudevents/spec/pull/123\",\n" + + " \"id\" : \"A234-1234-1234\",\n" + + " \"time\" : \"2018-04-05T17:31:00Z\",\n" + + " \"sampledrate\" : \"1\",\n" + + " \"contenttype\" : \"text/plain\",\n" + + " \"data\" : \"test\"\n" + + "}"; + + [Fact] + public void DistTraceParse() + { + var jsonFormatter = new JsonEventFormatter(); + var cloudEvent = jsonFormatter.DecodeStructuredEvent(Encoding.UTF8.GetBytes(jsonDistTrace), new DistributedTracingExtension()); + Assert.Equal("0.2", cloudEvent.SpecVersion); + Assert.Equal("com.github.pull.create", cloudEvent.Type); + Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), cloudEvent.Source); + Assert.Equal("A234-1234-1234", cloudEvent.Id); + Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), + cloudEvent.Time.Value.ToUniversalTime()); + + Assert.Equal("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", cloudEvent.Extension().TraceParent); + Assert.Equal("rojo=00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01,congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4=", cloudEvent.Extension().TraceState); + } + + [Fact] + public void DistTraceJsonTranscode() + { + var jsonFormatter = new JsonEventFormatter(); + var cloudEvent1 = jsonFormatter.DecodeStructuredEvent(Encoding.UTF8.GetBytes(jsonDistTrace)); + var jsonData = jsonFormatter.EncodeStructuredEvent(cloudEvent1, out var contentType); + var cloudEvent = jsonFormatter.DecodeStructuredEvent(jsonData, new DistributedTracingExtension()); + + Assert.Equal("0.2", cloudEvent.SpecVersion); + Assert.Equal("com.github.pull.create", cloudEvent.Type); + Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), cloudEvent.Source); + Assert.Equal("A234-1234-1234", cloudEvent.Id); + Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), + cloudEvent.Time.Value.ToUniversalTime()); + + Assert.Equal("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", cloudEvent.Extension().TraceParent); + Assert.Equal("rojo=00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01,congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4=", cloudEvent.Extension().TraceState); + } + + [Fact] + public void SequenceParse() + { + var jsonFormatter = new JsonEventFormatter(); + var cloudEvent = jsonFormatter.DecodeStructuredEvent(Encoding.UTF8.GetBytes(jsonSequence), new SequenceExtension()); + Assert.Equal("0.2", cloudEvent.SpecVersion); + Assert.Equal("com.github.pull.create", cloudEvent.Type); + Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), cloudEvent.Source); + Assert.Equal("A234-1234-1234", cloudEvent.Id); + Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), + cloudEvent.Time.Value.ToUniversalTime()); + + Assert.Equal("Integer", cloudEvent.Extension().SequenceType); + Assert.Equal("25", cloudEvent.Extension().Sequence); + } + + [Fact] + public void SequenceJsonTranscode() + { + var jsonFormatter = new JsonEventFormatter(); + var cloudEvent1 = jsonFormatter.DecodeStructuredEvent(Encoding.UTF8.GetBytes(jsonSequence)); + var jsonData = jsonFormatter.EncodeStructuredEvent(cloudEvent1, out var contentType); + var cloudEvent = jsonFormatter.DecodeStructuredEvent(jsonData, new SequenceExtension()); + + Assert.Equal("0.2", cloudEvent.SpecVersion); + Assert.Equal("com.github.pull.create", cloudEvent.Type); + Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), cloudEvent.Source); + Assert.Equal("A234-1234-1234", cloudEvent.Id); + Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), + cloudEvent.Time.Value.ToUniversalTime()); + + Assert.Equal("Integer", cloudEvent.Extension().SequenceType); + Assert.Equal("25", cloudEvent.Extension().Sequence); + } + + [Fact] + public void IntegerSequenceParse() + { + var jsonFormatter = new JsonEventFormatter(); + var cloudEvent = jsonFormatter.DecodeStructuredEvent(Encoding.UTF8.GetBytes(jsonSequence), new IntegerSequenceExtension()); + Assert.Equal("0.2", cloudEvent.SpecVersion); + Assert.Equal("com.github.pull.create", cloudEvent.Type); + Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), cloudEvent.Source); + Assert.Equal("A234-1234-1234", cloudEvent.Id); + Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), + cloudEvent.Time.Value.ToUniversalTime()); + + Assert.Equal(25, cloudEvent.Extension().Sequence); + } + + [Fact] + public void IntegerSequenceJsonTranscode() + { + var jsonFormatter = new JsonEventFormatter(); + var cloudEvent1 = jsonFormatter.DecodeStructuredEvent(Encoding.UTF8.GetBytes(jsonSequence)); + var jsonData = jsonFormatter.EncodeStructuredEvent(cloudEvent1, out var contentType); + var cloudEvent = jsonFormatter.DecodeStructuredEvent(jsonData, new IntegerSequenceExtension()); + + Assert.Equal("0.2", cloudEvent.SpecVersion); + Assert.Equal("com.github.pull.create", cloudEvent.Type); + Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), cloudEvent.Source); + Assert.Equal("A234-1234-1234", cloudEvent.Id); + Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), + cloudEvent.Time.Value.ToUniversalTime()); + + Assert.Equal(25, cloudEvent.Extension().Sequence); + } + + [Fact] + public void SamplingParse() + { + var jsonFormatter = new JsonEventFormatter(); + var cloudEvent = jsonFormatter.DecodeStructuredEvent(Encoding.UTF8.GetBytes(jsonSampledRate), new SamplingExtension()); + Assert.Equal("0.2", cloudEvent.SpecVersion); + Assert.Equal("com.github.pull.create", cloudEvent.Type); + Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), cloudEvent.Source); + Assert.Equal("A234-1234-1234", cloudEvent.Id); + Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), + cloudEvent.Time.Value.ToUniversalTime()); + + Assert.Equal(1, cloudEvent.Extension().SampledRate.Value); + } + + [Fact] + public void SamplingJsonTranscode() + { + var jsonFormatter = new JsonEventFormatter(); + var cloudEvent1 = jsonFormatter.DecodeStructuredEvent(Encoding.UTF8.GetBytes(jsonSampledRate)); + var jsonData = jsonFormatter.EncodeStructuredEvent(cloudEvent1, out var contentType); + var cloudEvent = jsonFormatter.DecodeStructuredEvent(jsonData, new SamplingExtension()); + + Assert.Equal("0.2", cloudEvent.SpecVersion); + Assert.Equal("com.github.pull.create", cloudEvent.Type); + Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), cloudEvent.Source); + Assert.Equal("A234-1234-1234", cloudEvent.Id); + Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), + cloudEvent.Time.Value.ToUniversalTime()); + + Assert.Equal(1, cloudEvent.Extension().SampledRate.Value); + } + } +} \ No newline at end of file