diff --git a/src/Core/HubSpotBaseClient.cs b/src/Core/HubSpotBaseClient.cs index 3ae1513..2ac2877 100644 --- a/src/Core/HubSpotBaseClient.cs +++ b/src/Core/HubSpotBaseClient.cs @@ -9,6 +9,7 @@ using RapidCore.Network; using Skarp.HubSpotClient.Core.Interfaces; using Skarp.HubSpotClient.Core.Requests; +using Skarp.HubSpotClient.CustomObjects.Interfaces; namespace Skarp.HubSpotClient.Core { @@ -46,19 +47,21 @@ protected async Task PostAsync(string absoluteUriPath, IHubSpotEntity enti where T : IHubSpotEntity, new() { Logger.LogDebug("Post async for uri path: '{0}' with type: '{1}'", absoluteUriPath, entity.GetType().Name); - var httpMethod = HttpMethod.Post; - - return await PutOrPost(absoluteUriPath, entity, true); + return await SerializeAndRunRequest(absoluteUriPath, entity, HttpMethod.Post); } protected async Task PutAsync(string absoluteUriPath, IHubSpotEntity entity) where T : IHubSpotEntity, new() { Logger.LogDebug("Post async for uri path: '{0}' with type: '{1}'", absoluteUriPath, entity.GetType().Name); - var json = _serializer.SerializeEntity(entity); - var httpMethod = HttpMethod.Post; + return await SerializeAndRunRequest(absoluteUriPath, entity, HttpMethod.Put); + } - return await PutOrPost(absoluteUriPath, entity, false); + protected async Task PatchAsync(string absoluteUriPath, IHubSpotEntity entity) + where T : ICustomObjectHubSpotEntity, IHubSpotEntity, new() + { + Logger.LogDebug("Patch async for uri path: '{0}' with type: '{1}'", absoluteUriPath, entity.GetType().Name); + return await SerializeAndRunRequest(absoluteUriPath, entity, new HttpMethod("PATCH")); } /// @@ -67,16 +70,16 @@ protected async Task PutAsync(string absoluteUriPath, IHubSpotEntity entit /// /// /// - /// + /// /// - private async Task PutOrPost(string absoluteUriPath, IHubSpotEntity entity, bool usePost) + private async Task SerializeAndRunRequest(string absoluteUriPath, IHubSpotEntity entity, HttpMethod httpMethod) where T : IHubSpotEntity, new() { var json = _serializer.SerializeEntity(entity); var data = await SendRequestAsync( absoluteUriPath, - usePost ? HttpMethod.Post : HttpMethod.Put, + httpMethod, json, responseData => (T)_serializer.DeserializeEntity(responseData)); diff --git a/src/Core/Requests/RequestDataConverter.cs b/src/Core/Requests/RequestDataConverter.cs index 53ca1b1..e79228b 100644 --- a/src/Core/Requests/RequestDataConverter.cs +++ b/src/Core/Requests/RequestDataConverter.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Dynamic; using System.Linq; using System.Reflection; using Microsoft.Extensions.Logging; using Skarp.HubSpotClient.Core.Interfaces; +using Skarp.HubSpotClient.CustomObjects.Interfaces; namespace Skarp.HubSpotClient.Core.Requests { @@ -50,6 +50,7 @@ public dynamic ToHubspotDataEntity(IHubSpotEntity entity, bool preformConversion // string value of it, but simply pass the object along - it will be serialized later as JSON... var propValue = prop.GetValue(entity); var value = propValue.IsComplexType() ? propValue : propValue?.ToString(); + var item = new HubspotDataEntityProp { Property = propSerializedName, @@ -76,6 +77,44 @@ public dynamic ToHubspotDataEntity(IHubSpotEntity entity, bool preformConversion return mapped; } + /// + /// Converts the given to a hubspot data entity. + /// + /// The entity. + /// + public dynamic ToHubspotDataCustomEntity(IHubSpotEntity entity) + { + _logger.LogDebug("Convert ToHubspotDataCustomEntity"); + dynamic mapped = new ExpandoObject(); + var propDictionary = new Dictionary(); + mapped.Properties = propDictionary; + + _logger.LogDebug("Use nameValue mapping?: {0}", entity.IsNameValue); + + var allProps = entity.GetType().GetProperties(); + _logger.LogDebug("Have {0} props to map", allProps.Length); + + foreach (var prop in allProps) + { + if (prop.HasIgnoreDataMemberAttribute() || prop.Name.Equals("id", StringComparison.CurrentCultureIgnoreCase)) { continue; } + + var propSerializedName = prop.GetPropSerializedName(); + _logger.LogDebug("Mapping prop: '{0}' with serialization name: '{1}'", prop.Name, propSerializedName); + if (prop.Name.Equals("RouteBasePath") || prop.Name.Equals("IsNameValue") || prop.Name.Equals("ObjectTypeId")) { continue; } + + // IF we have an complex type on the entity that we are trying to convert, let's NOT get the + // string value of it, but simply pass the object along - it will be serialized later as JSON... + var propValue = prop.GetValue(entity); + var value = propValue.IsComplexType() ? propValue : propValue?.ToString(); + + propDictionary.Add(propSerializedName, value); + } + + _logger.LogDebug("Mapping complete, returning data"); + + return mapped; + } + /// /// Converts the given to a simple list of name/values. /// @@ -120,11 +159,12 @@ public IEnumerable ToNameValueList(object entity) } /// - /// Convert from the dynamicly typed into a strongly typed + /// Convert from the dynamically typed into a strongly typed /// /// The representation of the returned json + /// /// - public T FromHubSpotResponse(ExpandoObject dynamicObject) where T : IHubSpotEntity, new() + public T FromHubSpotResponse(ExpandoObject dynamicObject, bool isCustomObject = false) where T : IHubSpotEntity, new() { var data = (T)ConvertSingleEntity(dynamicObject, new T()); return data; @@ -169,7 +209,7 @@ public IEnumerable ToNameValueList(object entity) // Convert all the entities var jsonEntities = expandoDict[propSerializedName]; - foreach (var entry in jsonEntities as List) + foreach (var entry in (List) jsonEntities) { // convert single entity var expandoEntry = entry as ExpandoObject; @@ -236,6 +276,14 @@ internal object ConvertSingleEntity(ExpandoObject dynamicObject, object dto) var expandoDict = (IDictionary)dynamicObject; var dtoProps = dto.GetType().GetProperties(); + // custom objects are returned with "Id" + if (expandoDict.TryGetValue("id", out var cId)) + { + // TODO use properly serialized name of prop to find it + var vidProp = dtoProps.SingleOrDefault(q => q.GetPropSerializedName() == "Id"); + vidProp?.SetValue(dto, Convert.ToInt64(cId)); + } + // The vid is the "id" of the entity if (expandoDict.TryGetValue("vid", out var vidData)) { @@ -269,32 +317,29 @@ internal object ConvertSingleEntity(ExpandoObject dynamicObject, object dto) } // The Properties object in the json / response data contains all the props we wish to map - if that does not exist - // we cannot proceeed + // we cannot proceed if (!expandoDict.TryGetValue("properties", out var dynamicProperties)) return dto; - foreach (var dynamicProp in dynamicProperties as ExpandoObject) + foreach (var dynamicProp in (ExpandoObject) dynamicProperties) { // prop.Key contains the name of the property we wish to map into the DTO // prop.Value contains the data returned by HubSpot, which is also an object // in there we need to go get the "value" prop to get the actual value _logger.LogDebug("Looking at dynamic prop: {0}", dynamicProp.Key); - if (!((IDictionary)dynamicProp.Value).TryGetValue("value", out object dynamicValue)) - { - continue; - } + object dynamicValue = null; + + dynamicValue = dynamicProp.Value is IDictionary objects ? objects.TryGetValue("value", out dynamicValue) : dynamicProp.Value; // TODO use properly serialized name of prop to find and set it's value - var targetProp = - dtoProps.SingleOrDefault(q => q.GetPropSerializedName() == dynamicProp.Key); - _logger.LogDebug("Have target prop? '{0}' with name: '{1}' and actual value: '{2}'", targetProp != null, - dynamicProp.Key, dynamicValue); + var targetProp = dtoProps.SingleOrDefault(q => q.GetPropSerializedName() == dynamicProp.Key); + _logger.LogDebug("Have target prop? '{0}' with name: '{1}' and actual value: '{2}'", targetProp != null, dynamicProp.Key, dynamicValue); if (targetProp != null) { // property type var pt = targetProp.PropertyType; - var val = dynamicValue.ToString(); + var val = dynamicValue?.ToString(); // skip any nullable properties with a empty value if (Nullable.GetUnderlyingType(pt) != null && string.IsNullOrEmpty(val)) diff --git a/src/Core/Requests/RequestSerializer.cs b/src/Core/Requests/RequestSerializer.cs index 64527d1..fff2313 100644 --- a/src/Core/Requests/RequestSerializer.cs +++ b/src/Core/Requests/RequestSerializer.cs @@ -4,6 +4,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Skarp.HubSpotClient.Core.Interfaces; +using Skarp.HubSpotClient.CustomObjects.Interfaces; namespace Skarp.HubSpotClient.Core.Requests { @@ -38,7 +39,7 @@ public RequestSerializer( /// /// Serializes the entity to JSON. /// - /// The entity. + /// The entity. /// The serialized entity public virtual string SerializeEntity(object obj) { @@ -48,37 +49,43 @@ public virtual string SerializeEntity(object obj) /// /// Serializes the entity to JSON. /// - /// The entity. - /// This by defines whether or not a data conversion is performed on the object so that the HubSpot "property-value" syntax will be used for serialization. If this parameter is set to false no data conversion is performed and a standard object serialization is performed. + /// The entity. + /// This by defines whether or not a data conversion is performed on the object so that the HubSpot "property-value" syntax will be used for serialization. If this parameter is set to false no data conversion is performed and a standard object serialization is performed. /// The serialized entity public virtual string SerializeEntity(object obj, bool performConversion) { - if (obj is IHubSpotEntity entity) + switch (obj) { - var converted = _requestDataConverter.ToHubspotDataEntity(entity, performConversion); + case ICustomObjectHubSpotEntity customEntity: + { + var converted = _requestDataConverter.ToHubspotDataCustomEntity(customEntity); + customEntity.ToHubSpotDataEntity(ref converted); - entity.ToHubSpotDataEntity(ref converted); + return JsonConvert.SerializeObject(converted, _jsonSerializerSettings); + } + case IHubSpotEntity entity: + { + var converted = _requestDataConverter.ToHubspotDataEntity(entity, performConversion); - return JsonConvert.SerializeObject( - converted, - _jsonSerializerSettings); - } + entity.ToHubSpotDataEntity(ref converted); - return JsonConvert.SerializeObject( - obj, - _jsonSerializerSettings); + return JsonConvert.SerializeObject(converted,_jsonSerializerSettings); + } + default: + return JsonConvert.SerializeObject(obj, _jsonSerializerSettings); + } } /// /// Serializes entity list to JSON. /// - /// The list of entities. + /// The list of entities. /// The serialized list of entities public virtual string SerializeEntities(List objs) { var result = new StringBuilder("["); - for (int i = 0; i < objs.Count; i++) + for (var i = 0; i < objs.Count; i++) { var obj = objs[i]; if (obj is IHubSpotEntity entity) @@ -110,13 +117,13 @@ public virtual string SerializeEntities(List objs) /// /// Serializes entity list to name/value JSON. /// - /// The list of entities. + /// The list of entities. /// The serialized list of entities public virtual string SerializeEntitiesToNameValueList(IList objs) { var result = new StringBuilder("["); - for (int i = 0; i < objs.Count; i++) + for (var i = 0; i < objs.Count; i++) { var obj = objs[i]; diff --git a/src/CustomObjects/CustomObjectRequestOptions.cs b/src/CustomObjects/CustomObjectRequestOptions.cs new file mode 100644 index 0000000..6b06691 --- /dev/null +++ b/src/CustomObjects/CustomObjectRequestOptions.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace Skarp.HubSpotClient.CustomObjects +{ + public class CustomObjectRequestOptions + { + private int _numberOfItemsToReturn = 20; + + /// + /// Gets or sets the number of custom objects to return. + /// + /// + /// Defaults to 20 which is also the hubspot api default. Max value is 100 + /// + /// + /// The number of custom objects to return. + /// + public int NumberOfObjectsToReturn + { + get => _numberOfItemsToReturn; + set + { + if (value < 1 || value > 100) + { + throw new ArgumentException( + $"Number of custom objects to return must be a positive integer greater than 0 - you provided {value}"); + } + _numberOfItemsToReturn = value; + } + } + + /// + /// Get or set the continuation offset when calling list many times to enumerate all your items + /// + /// + /// The return DTO from List of objects the current "offset" that you can inject into your next list call + /// to continue the listing process + /// + public long? ItemsOffset { get; set; } = null; + public List PropertiesToInclude { get; set; } = new List(); + public bool UseCustomKeyProperty { get; set; } + } +} diff --git a/src/CustomObjects/Dto/CustomObjectHubSpotEntity.cs b/src/CustomObjects/Dto/CustomObjectHubSpotEntity.cs new file mode 100644 index 0000000..2e47102 --- /dev/null +++ b/src/CustomObjects/Dto/CustomObjectHubSpotEntity.cs @@ -0,0 +1,13 @@ +using Skarp.HubSpotClient.CustomObjects.Interfaces; + +namespace Skarp.HubSpotClient.CustomObjects.Dto; + +public class CustomObjectHubSpotEntity : ICustomObjectHubSpotEntity +{ + public long? Id { get; set; } + public string ObjectTypeId { get; set; } + public string RouteBasePath => "/crm/v3/objects"; + public bool IsNameValue => false; + public void ToHubSpotDataEntity(ref dynamic dataEntity) { } + public void FromHubSpotDataEntity(dynamic hubspotData) { } +} \ No newline at end of file diff --git a/src/CustomObjects/HubSpotCustomObjectClient.cs b/src/CustomObjects/HubSpotCustomObjectClient.cs new file mode 100644 index 0000000..7b4f6a8 --- /dev/null +++ b/src/CustomObjects/HubSpotCustomObjectClient.cs @@ -0,0 +1,133 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using RapidCore.Network; +using Skarp.HubSpotClient.Core; +using Skarp.HubSpotClient.Core.Interfaces; +using Skarp.HubSpotClient.Core.Requests; +using Skarp.HubSpotClient.CustomObjects.Interfaces; +using System.Net.Http; +using System; +using System.Linq; +using Flurl; +using Skarp.HubSpotClient.Contact; +using Skarp.HubSpotClient.Contact.Dto; +using Skarp.HubSpotClient.CustomObjects.Dto; + +namespace Skarp.HubSpotClient.CustomObjects; + +public class HubSpotCustomObjectClient : HubSpotBaseClient , IHubSpotCustomObjectClient +{ + /// + /// Mockable and container ready constructor + /// + /// + /// + /// + /// + /// + public HubSpotCustomObjectClient( + IRapidHttpClient httpClient, + ILogger logger, + RequestSerializer serializer, + string hubSpotBaseUrl, + string apiKey) + : base(httpClient, logger, serializer, hubSpotBaseUrl, apiKey) + { + } + + /// + /// Create a new instance of the HubSpotCustomObjectClient with default dependencies + /// + /// + /// This constructor creates a HubSpotCustomObjectClient using "real" dependencies that will send requests + /// via the network - if you wish to have support for functional tests and mocking use the "eager" constructor + /// that takes in all underlying dependencies + /// + /// Your API key + public HubSpotCustomObjectClient(string apiKey) + : base( + new RealRapidHttpClient(new HttpClient()), + NoopLoggerFactory.Get(), + new RequestSerializer(new RequestDataConverter(NoopLoggerFactory.Get())), + "https://api.hubapi.com", + apiKey) + { } + + public async Task CreateAsync(ICustomObjectHubSpotEntity entity) where T : ICustomObjectHubSpotEntity, IHubSpotEntity, new() + { + Logger.LogDebug("Custom Object CreateAsync"); + var path = PathResolver(entity, HubSpotAction.Create); + var data = await PostAsync(path, entity); + return data; + } + + public async Task UpdateAsync(ICustomObjectHubSpotEntity entity) where T : ICustomObjectHubSpotEntity, IHubSpotEntity, new() + { + Logger.LogDebug("Custom object update w. id: {0}", entity.Id); + if (entity.Id < 1) + { + throw new ArgumentException("Custom object entity must have an id set!"); + } + var path = PathResolver(entity, HubSpotAction.Update) + .Replace(":customObjectId:", entity.Id.ToString()); + + return await PatchAsync(path, entity); + } + + public async Task DeleteAsync(ICustomObjectHubSpotEntity entity) + { + Logger.LogDebug("Custom object delete w. id: {0}", entity.Id); + + var path = PathResolver(entity, HubSpotAction.Delete) + .Replace(":customObjectId:", entity.Id.ToString()); + + await DeleteAsync(path); + } + + public async Task GetByIdAsync(long customObjectId, CustomObjectRequestOptions opts = null) where T : ICustomObjectHubSpotEntity, IHubSpotEntity, new() + { + Logger.LogDebug("Custom object get by id "); + var path = PathResolver(new T(), HubSpotAction.Get) + .Replace(":customObjectId:", customObjectId.ToString()); + + path = AddGetRequestOptions(path, opts); + var data = await GetAsync(path); + return data; + } + + /// + /// Resolve a hubspot API path based off the entity and operation that is about to happen + /// + /// + /// + /// + /// + public string PathResolver(ICustomObjectHubSpotEntity entity, HubSpotAction action) + { + if (string.IsNullOrEmpty(entity.ObjectTypeId)) + throw new ArgumentException($"Property {nameof(entity.ObjectTypeId)} must have a valid value"); + + return action switch + { + HubSpotAction.Create => $"{entity.RouteBasePath}/{entity.ObjectTypeId}", + HubSpotAction.Get => $"{entity.RouteBasePath}/{entity.ObjectTypeId}/:customObjectId:", + HubSpotAction.List => $"{entity.RouteBasePath}/{entity.ObjectTypeId}", + HubSpotAction.Update => $"{entity.RouteBasePath}/{entity.ObjectTypeId}/:customObjectId:", + HubSpotAction.Delete => $"{entity.RouteBasePath}/{entity.ObjectTypeId}/:customObjectId:", + _ => throw new ArgumentOutOfRangeException(nameof(action), action, null) + }; + } + + private static string AddGetRequestOptions(string path, CustomObjectRequestOptions opts = null) + { + var newPath = path; + + opts ??= new CustomObjectRequestOptions(); + if (opts.PropertiesToInclude.Any()) + { + newPath = newPath.SetQueryParam("properties", opts.PropertiesToInclude); + } + + return newPath; + } +} \ No newline at end of file diff --git a/src/CustomObjects/Interfaces/ICustomObjectHubSpotEntity.cs b/src/CustomObjects/Interfaces/ICustomObjectHubSpotEntity.cs new file mode 100644 index 0000000..23cf4a9 --- /dev/null +++ b/src/CustomObjects/Interfaces/ICustomObjectHubSpotEntity.cs @@ -0,0 +1,13 @@ +using Skarp.HubSpotClient.Core.Interfaces; + +namespace Skarp.HubSpotClient.CustomObjects.Interfaces; + +public interface ICustomObjectHubSpotEntity : IHubSpotEntity +{ + long? Id { get; set; } + /// + /// The Id Hubspot gives to your custom object when you create it for the first time + /// + string ObjectTypeId { get; set; } + string RouteBasePath { get; } +} \ No newline at end of file diff --git a/src/CustomObjects/Interfaces/IHubSpotCustomObjectClient.cs b/src/CustomObjects/Interfaces/IHubSpotCustomObjectClient.cs new file mode 100644 index 0000000..57b3ea0 --- /dev/null +++ b/src/CustomObjects/Interfaces/IHubSpotCustomObjectClient.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; +using Skarp.HubSpotClient.Core.Interfaces; + +namespace Skarp.HubSpotClient.CustomObjects.Interfaces; + +public interface IHubSpotCustomObjectClient +{ + /// + /// Creates a custom object entity asynchronously. + /// + /// The entity. + /// + /// + Task CreateAsync(ICustomObjectHubSpotEntity entity) where T : ICustomObjectHubSpotEntity, IHubSpotEntity, new(); + /// + /// Update an existing custom object in hubspot + /// + /// + /// + Task UpdateAsync(ICustomObjectHubSpotEntity entity) where T : ICustomObjectHubSpotEntity, IHubSpotEntity, new(); + /// + /// Delete an existing custom object in hubspot by id + /// + /// + /// + Task DeleteAsync(ICustomObjectHubSpotEntity entity); + /// + /// Return a single custom object by id from hubspot + /// + /// + /// Options for enabling/disabling history and specifying properties + /// + /// + Task GetByIdAsync(long customObjectId, CustomObjectRequestOptions opts = null) where T : ICustomObjectHubSpotEntity, IHubSpotEntity, new(); + +} diff --git a/src/LineItem/Interfaces/IHubSpotLineItemClient.cs b/src/LineItem/Interfaces/IHubSpotLineItemClient.cs index f9eaef4..6aadab5 100644 --- a/src/LineItem/Interfaces/IHubSpotLineItemClient.cs +++ b/src/LineItem/Interfaces/IHubSpotLineItemClient.cs @@ -20,7 +20,7 @@ public interface IHubSpotLineItemClient /// The entities. /// /// - Task> CreateBatchAsync(IEnumerable entity) where T : IHubSpotEntity, new(); + Task> CreateBatchAsync(IEnumerable entities) where T : IHubSpotEntity, new(); /// /// Delete a line item from hubspot /// diff --git a/src/hubspot-client.csproj b/src/hubspot-client.csproj index 4e55a46..eca31ab 100644 --- a/src/hubspot-client.csproj +++ b/src/hubspot-client.csproj @@ -41,6 +41,10 @@ + + + + true diff --git a/test/integration/CustomObject/Dto/CustomObjectHubSpotEntityExtended.cs b/test/integration/CustomObject/Dto/CustomObjectHubSpotEntityExtended.cs new file mode 100644 index 0000000..a0943d6 --- /dev/null +++ b/test/integration/CustomObject/Dto/CustomObjectHubSpotEntityExtended.cs @@ -0,0 +1,16 @@ +using Skarp.HubSpotClient.CustomObjects.Dto; + +namespace integration.CustomObject.Dto; + +/// +/// Create +/// +public class CustomObjectHubSpotEntityExtended : CustomObjectHubSpotEntity +{ + public CustomObjectHubSpotEntityExtended() + { + ObjectTypeId = "2-6013641"; + } + + public string my_object_property { get; set; } +} \ No newline at end of file diff --git a/test/integration/CustomObject/HubSpotCustomObjectClientIntegrationTest.cs b/test/integration/CustomObject/HubSpotCustomObjectClientIntegrationTest.cs new file mode 100644 index 0000000..0e004ea --- /dev/null +++ b/test/integration/CustomObject/HubSpotCustomObjectClientIntegrationTest.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using integration.CustomObject.Dto; +using Microsoft.Extensions.Logging; +using RapidCore.Network; +using Skarp.HubSpotClient.Core.Requests; +using Skarp.HubSpotClient.CustomObjects; +using Xunit; +using Xunit.Abstractions; + +namespace integration.CustomObject; + +public class HubSpotCustomObjectClientIntegrationTest : IntegrationTestBase +{ + private readonly HubSpotCustomObjectClient _client; + private readonly string _apiKey; + private readonly bool _isAppVeyorEnv; + + public HubSpotCustomObjectClientIntegrationTest(ITestOutputHelper output) : base(output) + { + _apiKey = Environment.GetEnvironmentVariable("HUBSPOT_API_KEY") ?? "demo"; + _isAppVeyorEnv = (Environment.GetEnvironmentVariable("APPVEYOR") ?? "false").Equals("true", StringComparison.InvariantCultureIgnoreCase); + _client = new HubSpotCustomObjectClient(new RealRapidHttpClient(new HttpClient()), Logger, + new RequestSerializer(new RequestDataConverter(LoggerFactory.CreateLogger())), + "https://api.hubapi.com", + _apiKey); + } + + [Fact] + public async Task Create_custom_object() + { + if (_apiKey.Equals("demo") && _isAppVeyorEnv) + { + Output.WriteLine("Skipping test as the API key is incorrectly set and we're in AppVeyor"); + Assert.True(true); + return; + } + + var customObject = new CustomObjectHubSpotEntityExtended() + { + my_object_property = "Test 5" + }; + var created = await _client.CreateAsync(customObject); + + Assert.True(created?.Id > 0); + } + + [Fact] + public async Task Create_custom_object_and_retrieve_it_using_id() + { + if (_apiKey.Equals("demo") && _isAppVeyorEnv) + { + Output.WriteLine("Skipping test as the API key is incorrectly set and we're in AppVeyor"); + Assert.True(true); + return; + } + + var customObject = new CustomObjectHubSpotEntityExtended() + { + my_object_property = "Test 5" + }; + var created = await _client.CreateAsync(customObject); + + Assert.True(created?.Id > 0); + + var retrieved = await _client.GetByIdAsync(created.Id.Value); + + Assert.NotNull(retrieved); + } + + [Fact] + public async Task Create_custom_object_and_retrieve_with_custom_properties() + { + if (_apiKey.Equals("demo") && _isAppVeyorEnv) + { + Output.WriteLine("Skipping test as the API key is incorrectly set and we're in AppVeyor"); + Assert.True(true); + return; + } + + var customObject = new CustomObjectHubSpotEntityExtended() + { + my_object_property = "Test 5" + }; + var created = await _client.CreateAsync(customObject); + + Assert.True(created?.Id > 0); + + var retrieved = await _client.GetByIdAsync(created.Id.Value, new CustomObjectRequestOptions() + { + PropertiesToInclude = new List() + { + "my_object_property" + } + }); + + Assert.True(retrieved.my_object_property == customObject.my_object_property); + } + + [Fact] + public async Task Create_custom_object_and_update_property() + { + if (_apiKey.Equals("demo") && _isAppVeyorEnv) + { + Output.WriteLine("Skipping test as the API key is incorrectly set and we're in AppVeyor"); + Assert.True(true); + return; + } + + var customObject = new CustomObjectHubSpotEntityExtended() + { + my_object_property = "Test 5" + }; + var created = await _client.CreateAsync(customObject); + + Assert.True(created?.Id > 0); + var newValue = Guid.NewGuid().ToString(); + created.my_object_property = newValue; + var updated = await _client.UpdateAsync(created); + Assert.True(updated.my_object_property == newValue); + } + + [Fact] + public async Task Create_custom_object_and_delete_it() + { + if (_apiKey.Equals("demo") && _isAppVeyorEnv) + { + Output.WriteLine("Skipping test as the API key is incorrectly set and we're in AppVeyor"); + Assert.True(true); + return; + } + + var customObject = new CustomObjectHubSpotEntityExtended() + { + my_object_property = "Test 5" + }; + var created = await _client.CreateAsync(customObject); + + Assert.True(created?.Id > 0); + + await _client.DeleteAsync(created); + + var deleted = await _client.GetByIdAsync(created.Id.Value); + + Assert.Null(deleted); + } +} \ No newline at end of file diff --git a/test/integration/integration.csproj b/test/integration/integration.csproj index f6261b0..e9b8f13 100644 --- a/test/integration/integration.csproj +++ b/test/integration/integration.csproj @@ -16,4 +16,7 @@ + + + \ No newline at end of file diff --git a/test/unit/Core/Requests/RequestDataConverterTest.cs b/test/unit/Core/Requests/RequestDataConverterTest.cs index 29dccd8..069b277 100644 --- a/test/unit/Core/Requests/RequestDataConverterTest.cs +++ b/test/unit/Core/Requests/RequestDataConverterTest.cs @@ -92,7 +92,7 @@ public void RequestDataConverter_converts_contact_custom_to_internal_representat var prop = props.Single(q => q.Property == "MyCustomProp"); - Assert.Equal(prop.Value, "Has a value!"); + Assert.Equal(prop.Value, $"Has a value!"); } [Fact]