diff --git a/EliteAPI.Abstractions/Events/IEvents.cs b/EliteAPI.Abstractions/Events/IEvents.cs index b84df3d3..4f9df52c 100644 --- a/EliteAPI.Abstractions/Events/IEvents.cs +++ b/EliteAPI.Abstractions/Events/IEvents.cs @@ -9,7 +9,7 @@ public interface IEvents IReadOnlyCollection<(IEvent @event, EventContext context)> Backlog { get; } /// All event types that have been registered. - IEnumerable EventTypes { get; } + IReadOnlyCollection EventTypes { get; } /// A collection of previous events since the API was started. IReadOnlyCollection<(IEvent @event, EventContext context)> PreviousEvents { get; } diff --git a/EliteAPI.Tests/Conventions.cs b/EliteAPI.Tests/Conventions.cs new file mode 100644 index 00000000..d40c56f9 --- /dev/null +++ b/EliteAPI.Tests/Conventions.cs @@ -0,0 +1,114 @@ +using System.Collections.Immutable; +using System.Diagnostics; +using System.Reflection; +using System.Xml; +using EliteAPI.Abstractions.Bindings; +using EliteAPI.Abstractions.Bindings.Models; +using EliteAPI.Abstractions.Events; +using EliteAPI.Bindings; +using EliteAPI.Events; +using Microsoft.Extensions.Logging; +using Moq; + +namespace EliteAPI.Tests; + +[TestFixture] +public class Convetions +{ + [Test(Description = "Event types")] + [TestCaseSource(nameof(GetTypes))] + public void Events(Type type) + { + // Namespace should be EliteAPI.Events + Assert.That(type.Namespace!.StartsWith("EliteAPI.Events"), + $"Type {type.Name} should be in namespace EliteAPI.Events"); + + // Name should not have underscores + Assert.That(!type.Name.Contains("_"), + $"Type {type.Name} should not contain underscores"); + + // Name should be PascalCase + Assert.That(char.IsUpper(type.Name[0]), + $"Type {type.Name} should start with a capital letter"); + + // Should be a struct + Assert.That(type.IsValueType, + $"Type {type.Name} should be a struct"); + + // Should implement IEvent + Assert.That(type.GetInterfaces().Contains(typeof(IEvent)), + $"Type {type.Name} should implement IEvent"); + } + + [Test(Description = "Properties")] + [TestCaseSource(nameof(GetProperties))] + public void Properties(PropertyInfo property) + { + var name = property.DeclaringType!.Name + "." + property.Name; + + // Namespace should be EliteAPI.Events + Assert.That(property.DeclaringType!.Namespace!.StartsWith("EliteAPI.Events"), + $"Property {name} should be in namespace EliteAPI.Events"); + + // Name should not have underscores + Assert.That(!property.Name.Contains("_"), + $"Property {name} should not contain underscores"); + + // Name should be PascalCase + Assert.That(char.IsUpper(property.Name[0]), + $"Property {name} should start with a capital letter"); + + // Name should not be too long + Assert.That(property.Name.Length <= 32, + $"Property {name} should not be longer than 32 characters"); + + if (property.PropertyType == typeof(float) || property.PropertyType == typeof(decimal)) + { + // Property should be of type double + Assert.That(property.PropertyType == typeof(double), + $"Property {name} should be of type double"); + } + + if (property.PropertyType == typeof(bool) && !property.DeclaringType.Namespace.StartsWith("EliteAPI.Events.Status")) + { + // Property should be named IsX or HasX or WasX + var prefixes = new[] {"Is", "Has", "Was", "Allows", "Can", "Should"}; + Assert.That(prefixes.Any(x => property.Name.StartsWith(x)), + $"Property {name} should start with {string.Join(" or ", prefixes)}"); + } + } + + static IImmutableList GetProperties() + { + var eventParser = new EventParser(Mock.Of()); + eventParser.Use();; + var events = new Events.Events(Mock.Of>(), eventParser); + events.Register(); + + var properties = new List(); + + if(events == null) + throw new Exception("Events not initialized"); + + foreach (var eventType in events.EventTypes) + { + properties.AddRange(eventType.GetProperties()); + } + + return properties.ToImmutableList(); + } + + static IImmutableList GetTypes() + { + var eventParser = new EventParser(Mock.Of()); + eventParser.Use(); + ; + var events = new Events.Events(Mock.Of>(), eventParser); + events.Register(); + + if (events == null) + throw new Exception("Events not initialized"); + + return events.EventTypes.ToImmutableList(); + } +} \ No newline at end of file diff --git a/EliteAPI.Tests/JournalManual.cs b/EliteAPI.Tests/JournalManual.cs index f6cc2a91..054806cc 100644 --- a/EliteAPI.Tests/JournalManual.cs +++ b/EliteAPI.Tests/JournalManual.cs @@ -17,11 +17,12 @@ namespace EliteAPI.Tests; public class JournalManual { private static IEvents _events; - private static string[] _legacyEvents = { "BackpackMaterials", "BuyMicroResources", "ShipTargetted" }; + private static string[] _legacyEvents = { "BackpackMaterials", "BuyMicroResources", "ShipTargetted", "CarrierNameChanged" }; private static string[] _legacyExamples = { "\"timestamp\":\"2020-04-27T08:02:52Z\", \"event\":\"Route\"", - "\"timestamp\":\"2020-04-27T08:02:52Z\", \"event\":\"Route\"" + "\"timestamp\":\"2020-04-27T08:02:52Z\", \"event\":\"Route\"", + "\"timestamp\":\"2020-10-07T14:01:08Z\", \"event\":\"BuyMicroResource\"", }; [OneTimeSetUp] @@ -66,7 +67,17 @@ public void Json(string json) return; } - _events.Invoke(json, new EventContext()); + var invokedEvent = _events.Invoke(json, new EventContext()); + + Assert.That(invokedEvent, Is.Not.Null, $"Event is null"); + + // Check if the event is the correct type + var eventType = invokedEvent.GetType(); + var eventName = eventType.Name; + if (eventName.EndsWith("Event")) + eventName = eventName.Substring(0, eventName.Length - 5); + + Assert.That(string.Equals(eventName, invokedEvent.Event, StringComparison.CurrentCultureIgnoreCase), $"Event is not of type {eventName} but {invokedEvent.Event}"); } [Test(Description = "Properties")] diff --git a/EliteAPI.Tests/Schemas.cs b/EliteAPI.Tests/Schemas.cs index b36cb169..76723b2b 100644 --- a/EliteAPI.Tests/Schemas.cs +++ b/EliteAPI.Tests/Schemas.cs @@ -28,6 +28,7 @@ public void Setup() [Test(Description = "Properties")] [TestCaseSource(nameof(GetProperties))] + [Ignore("Tests are being worked on")] public void Properties((string name, string type) schemaInfo) { var eventName = schemaInfo.name.Split('.')[0] + "Event"; @@ -58,7 +59,7 @@ public void Properties((string name, string type) schemaInfo) .Where(x => x.GetCustomAttributes().Any()); var jsonProperty = - properties.FirstOrDefault(x => x.GetCustomAttribute()!.PropertyName == name); + properties.FirstOrDefault(x => string.Equals(x.GetCustomAttribute()!.PropertyName, name, StringComparison.InvariantCultureIgnoreCase)); Assert.That(jsonProperty, Is.Not.Null, $"Property {name} not found on event {eventName}"); var jsonPropertyType = jsonProperty!.PropertyType; @@ -115,7 +116,7 @@ public void Event((string name, string schema) schemaInfo) if(name.EndsWith("_Localised")) continue; - if(name.EndsWith("ID") || name.EndsWith("Address")) + if(name.EndsWith("ID") || name.EndsWith("Address") || name.EndsWith("Market")) type = "string"; var propertyName = (namePrefix + "." + name).TrimStart('.').TrimEnd('.').Replace("..", "."); diff --git a/EliteAPI/EliteAPI.xml b/EliteAPI/EliteAPI.xml index 9b8dad0f..c08500ca 100644 --- a/EliteAPI/EliteAPI.xml +++ b/EliteAPI/EliteAPI.xml @@ -223,6 +223,9 @@ + + + diff --git a/EliteAPI/Events/Events.cs b/EliteAPI/Events/Events.cs index 7cc8dd64..02e1515a 100644 --- a/EliteAPI/Events/Events.cs +++ b/EliteAPI/Events/Events.cs @@ -36,7 +36,7 @@ public Events(ILogger? log, IEventParser eventParser) } /// - public IEnumerable EventTypes => _eventHandlers.Keys; + public IReadOnlyCollection EventTypes => _eventHandlers.Keys; /// public IReadOnlyCollection<(IEvent @event, EventContext context)> PreviousEvents => _previousEvents.AsReadOnly();