Initial extensions test coverage. Completed extensions

Signed-off-by: clemensv <clemensv@microsoft.com>
This commit is contained in:
clemensv 2018-11-27 17:15:52 +01:00
parent df4f79d40a
commit f0cfdd6322
15 changed files with 554 additions and 7 deletions

View File

@ -8,6 +8,9 @@ namespace CloudNative.CloudEvents
using System.Collections.Generic;
using System.Net.Mime;
/// <summary>
/// Represents a CloudEvent
/// </summary>
public class CloudEvent
{
public const string MediaType = "application/cloudevents";

View File

@ -10,6 +10,9 @@ namespace CloudNative.CloudEvents
using System.Globalization;
using System.Net.Mime;
/// <summary>
/// The CloudEvents attributes
/// </summary>
public class CloudEventAttributes : IDictionary<string, object>
{
public const string ContentTypeAttributeName = "contenttype";
@ -32,7 +35,7 @@ namespace CloudNative.CloudEvents
IEnumerable<ICloudEventExtension> extensions;
public CloudEventAttributes(IEnumerable<ICloudEventExtension> extensions)
internal CloudEventAttributes(IEnumerable<ICloudEventExtension> extensions)
{
this.extensions = extensions;
}

View File

@ -12,11 +12,21 @@ namespace CloudNative.CloudEvents
using System.Text;
using System.Threading.Tasks;
/// <summary>
/// This class is for use with `HttpClient` and constructs content and headers for
/// a HTTP request from a CloudEvent.
/// </summary>
public class CloudEventContent : HttpContent
{
IInnerContent inner;
static JsonEventFormatter jsonFormatter = new JsonEventFormatter();
/// <summary>
/// Constructor
/// </summary>
/// <param name="cloudEvent">CloudEvent</param>
/// <param name="contentMode">Content mode. Structured or binary.</param>
/// <param name="formatter">Event formatter</param>
public CloudEventContent(CloudEvent cloudEvent, ContentMode contentMode, ICloudEventFormatter formatter)
{
if (contentMode == ContentMode.Structured)

View File

@ -9,9 +9,13 @@ namespace CloudNative.CloudEvents
/// </summary>
public enum ContentMode
{
// Structured mode. The complete CloudEvent is contained in the transport body
/// <summary>
/// Structured mode. The complete CloudEvent is contained in the transport body
/// </summary>
Structured,
// Binary mode. The CloudEvent is projected onto the transport frame
/// <summary>
/// Binary mode. The CloudEvent is projected onto the transport frame
/// </summary>
Binary
}
}

View File

@ -15,7 +15,7 @@ namespace CloudNative.CloudEvents.Extensions
IDictionary<string, object> attributes = new Dictionary<string, object>();
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;
}

View File

@ -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;
}
}
}

View File

@ -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<string, object> attributes = new Dictionary<string, object>();
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;
}
}
}

View File

@ -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<string, object> attributes = new Dictionary<string, object>();
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;
}
}
}

View File

@ -21,6 +21,14 @@ namespace CloudNative.CloudEvents
const string SpecVersionHttpHeader = HttpHeaderPrefix + "specversion";
static JsonEventFormatter jsonFormatter = new JsonEventFormatter();
/// <summary>
/// Copies the CloudEvent into this HttpListenerResponse instance
/// </summary>
/// <param name="httpListenerResponse">this</param>
/// <param name="cloudEvent">CloudEvent to copy</param>
/// <param name="contentMode">Content mode (structured or binary)</param>
/// <param name="formatter">Formatter</param>
/// <returns>Task</returns>
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);
}
/// <summary>
/// Copies the CloudEvent into this HttpWebRequest instance
/// </summary>
/// <param name="httpWebRequest">this</param>
/// <param name="cloudEvent">CloudEvent to copy</param>
/// <param name="contentMode">Content mode (structured or binary)</param>
/// <param name="formatter">Formatter</param>
/// <returns>Task</returns>
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());
}
/// <summary>
/// Indicates whether this HttpResponseMessage holds a CloudEvent
/// </summary>
/// <param name="httpResponseMessage"></param>
/// <returns>true, if the response is a CloudEvent</returns>
public static bool IsCloudEvent(this HttpResponseMessage httpResponseMessage)
{
return ((httpResponseMessage.Content.Headers.ContentType != null &&
@ -65,6 +86,9 @@ namespace CloudNative.CloudEvents
httpResponseMessage.Headers.Contains(SpecVersionHttpHeader));
}
/// <summary>
/// Indicates whether this HttpListenerRequest holds a CloudEvent
/// </summary>
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));
}
/// <summary>
/// Converts this response message into a CloudEvent object, with the given extensions.
/// </summary>
/// <param name="httpResponseMessage">Response message</param>
/// <param name="extensions">List of extension instances</param>
/// <returns>A CloudEvent instance or 'null' if the response message doesn't hold a CloudEvent</returns>
public static CloudEvent ToCloudEvent(this HttpResponseMessage httpResponseMessage,
params ICloudEventExtension[] extensions)
{
return ToCloudEventInternal(httpResponseMessage, null, extensions);
}
/// <summary>
/// Converts this response message into a CloudEvent object, with the given extensions and
/// overriding the default formatter.
/// </summary>
/// <param name="httpResponseMessage">Response message</param>
/// <param name="formatter"></param>
/// <param name="extensions">List of extension instances</param>
/// <returns>A CloudEvent instance or 'null' if the response message doesn't hold a CloudEvent</returns>
public static CloudEvent ToCloudEvent(this HttpResponseMessage httpResponseMessage,
ICloudEventFormatter formatter, params ICloudEventExtension[] extensions)
{
return ToCloudEventInternal(httpResponseMessage, formatter, extensions);
}
/// <summary>
/// Converts this listener request into a CloudEvent object, with the given extensions.
/// </summary>
/// <param name="httpListenerRequest">Listener request</param>
/// <param name="extensions">List of extension instances</param>
/// <returns>A CloudEvent instance or 'null' if the response message doesn't hold a CloudEvent</returns>
public static CloudEvent ToCloudEvent(this HttpListenerRequest httpListenerRequest,
params ICloudEventExtension[] extensions)
{
return ToCloudEvent(httpListenerRequest, null, extensions);
}
/// <summary>
/// Converts this listener request into a CloudEvent object, with the given extensions,
/// overriding the formatter.
/// </summary>
/// <param name="httpListenerRequest">Listener request</param>
/// <param name="formatter"></param>
/// <param name="extensions">List of extension instances</param>
/// <returns>A CloudEvent instance or 'null' if the response message doesn't hold a CloudEvent</returns>
public static CloudEvent ToCloudEvent(this HttpListenerRequest httpListenerRequest,
ICloudEventFormatter formatter = null,
params ICloudEventExtension[] extensions)

View File

@ -6,6 +6,9 @@ namespace CloudNative.CloudEvents
{
using System;
/// <summary>
/// Implemented for extension objects that reflect CloudEvent extension specifications.
/// </summary>
public interface ICloudEventExtension
{
/// <summary>

View File

@ -9,12 +9,47 @@ namespace CloudNative.CloudEvents
using System.Net.Mime;
/// <summary>
/// Implemented by formatters
/// </summary>
public interface ICloudEventFormatter
{
/// <summary>
/// Decode a structured event from a stream
/// </summary>
/// <param name="data"></param>
/// <param name="extensions"></param>
/// <returns></returns>
CloudEvent DecodeStructuredEvent(Stream data, IEnumerable<ICloudEventExtension> extensions);
/// <summary>
/// Decode a structured event from a byte array
/// </summary>
/// <param name="data"></param>
/// <param name="extensions"></param>
/// <returns></returns>
CloudEvent DecodeStructuredEvent(byte[] data, IEnumerable<ICloudEventExtension> extensions);
/// <summary>
/// Encode an structured event into a byte array
/// </summary>
/// <param name="cloudEvent"></param>
/// <param name="contentType"></param>
/// <returns></returns>
byte[] EncodeStructuredEvent(CloudEvent cloudEvent, out ContentType contentType);
/// <summary>
/// Decode an attribute from a byte array
/// </summary>
/// <param name="name"></param>
/// <param name="data"></param>
/// <param name="extensions"></param>
/// <returns></returns>
object DecodeAttribute(string name, byte[] data, IEnumerable<ICloudEventExtension> extensions);
/// <summary>
/// Encode an attribute into a byte array
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
/// <param name="extensions"></param>
/// <returns></returns>
byte[] EncodeAttribute(string name, object value, IEnumerable<ICloudEventExtension> extensions);
}
}

View File

@ -12,6 +12,9 @@ namespace CloudNative.CloudEvents
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
/// <summary>
/// Formatter that implements the JSON Event Format
/// </summary>
public class JsonEventFormatter : ICloudEventFormatter
{

View File

@ -78,6 +78,15 @@ namespace CloudNative.CloudEvents {
}
}
/// <summary>
/// Looks up a localized string similar to The &apos;sampledrate&apos; attribute value must be an integer.
/// </summary>
internal static string ErrorSampledRateValueIsaNotAnInteger {
get {
return ResourceManager.GetString("ErrorSampledRateValueIsaNotAnInteger", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The &apos;schemaurl&apos; attribute value must be a valid absolute or relative URI.
/// </summary>
@ -87,6 +96,24 @@ namespace CloudNative.CloudEvents {
}
}
/// <summary>
/// Looks up a localized string similar to The &apos;sequencetype&apos; attribute value must be an integer.
/// </summary>
internal static string ErrorSequenceTypeValueIsaNotAString {
get {
return ResourceManager.GetString("ErrorSequenceTypeValueIsaNotAString", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The &apos;sequence&apos; attribute value must be an integer.
/// </summary>
internal static string ErrorSequenceValueIsaNotAString {
get {
return ResourceManager.GetString("ErrorSequenceValueIsaNotAString", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The &apos;source&apos; attribute value must be a valid absolute or relative URI.
/// </summary>

View File

@ -123,9 +123,18 @@
<data name="ErrorIdValueIsNotAString" xml:space="preserve">
<value>The 'id' attribute value must be a string</value>
</data>
<data name="ErrorSampledRateValueIsaNotAnInteger" xml:space="preserve">
<value>The 'sampledrate' attribute value must be an integer</value>
</data>
<data name="ErrorSchemaUrlIsNotAUri" xml:space="preserve">
<value>The 'schemaurl' attribute value must be a valid absolute or relative URI</value>
</data>
<data name="ErrorSequenceTypeValueIsaNotAString" xml:space="preserve">
<value>The 'sequencetype' attribute value must be an integer</value>
</data>
<data name="ErrorSequenceValueIsaNotAString" xml:space="preserve">
<value>The 'sequence' attribute value must be an integer</value>
</data>
<data name="ErrorSourceValueIsNotAUri" xml:space="preserve">
<value>The 'source' attribute value must be a valid absolute or relative URI</value>
</data>

View File

@ -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<DistributedTracingExtension>().TraceParent);
Assert.Equal("rojo=00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01,congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4=", cloudEvent.Extension<DistributedTracingExtension>().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<DistributedTracingExtension>().TraceParent);
Assert.Equal("rojo=00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01,congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4=", cloudEvent.Extension<DistributedTracingExtension>().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<SequenceExtension>().SequenceType);
Assert.Equal("25", cloudEvent.Extension<SequenceExtension>().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<SequenceExtension>().SequenceType);
Assert.Equal("25", cloudEvent.Extension<SequenceExtension>().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<IntegerSequenceExtension>().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<IntegerSequenceExtension>().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<SamplingExtension>().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<SamplingExtension>().SampledRate.Value);
}
}
}