Skip to content

Commit 2b9a0c5

Browse files
niemyjskiCopilot
andauthored
Add comprehensive serialization unit tests for all models (#358)
* Add comprehensive serialization unit tests for all models Add a Models/ folder under Serializer tests following the pattern from the server-side Exceptionless repo. Each model has its own test class with three-part named tests (Arrange/Act/Assert) covering: - Event (with nested data models, tags, special characters) - EnvironmentInfo (all properties, OS name/version JsonProperty attrs) - Error (nested inner errors, stack frames, modules, target method) - InnerError (chain nesting, target method, data dictionary) - SimpleError (modules, inner hierarchy, @ext key) - SimpleInnerError (nesting, null stack trace) - RequestInfo (headers, cookies, query string, post data, known keys) - UserInfo (identity, name, data, unicode, trimming) - UserDescription (email, description, data, trimming) - ManualStackingInfo (title, signature data, constructors) - ClientConfiguration (version, settings, case-insensitive keys) - Module (dates, is_entry, data dictionary) - Method (generic arguments, parameters, signature target) - StackFrame (file path, line/column, parameters, generic args) - Parameter (type namespace, generic arguments, data) - StackFrameCollection (ordering, complete frame properties) - ModuleCollection (multiple modules, data preservation) - ParameterCollection (multiple params with generic args) - GenericArguments (empty, single, multiple, ordering) - TagSet (case insensitivity, special characters) - DataDictionary (mixed types, key casing, @ prefixes) - SettingsDictionary (known keys, boolean/int parsing) - SerializerContractAssertions helper All 155 tests pass and validate: - Snake_case property naming in JSON output - Round-trip serialize/deserialize data preservation - Correct handling of nested objects and collections - Special character escaping and Unicode support - Known data key constants Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Overhaul serialization tests: base class, full JSON assertions, lang=json constants - Add SerializerTestBase with Serializer, Serialize(), Deserialize<T>(), RoundTrip<T>() helpers - Remove SerializerContractAssertions (partial assertions replaced with whole-string) - Rewrite all 22 model test classes to extend SerializerTestBase - All JSON strings are const with /* lang=json */ IDE hint comments - Assert.Equal(expected, actual) on full JSON strings — no more Contains() - Add DataDictionaryConverter tests: JObject→compact string, JArray→compact string - Add RequestInfoConverter test: JObject PostData→indented string - Add EnvironmentInfo o_s_name/o_s_version explicit [JsonProperty] attribute test - Add Event KnownDataKeys constants validation test - Add Theory tests for all Event.KnownTypes - Set LangVersion=11.0 in test project for raw string literals Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix RequestInfoConverter JArray PostData bug; fix lang=json placement Bug: RequestInfoConverter only checked 'PostData is JObject' — a JSON array body (e.g. REST POST with array payload) left PostData as a raw JArray instead of converting it to a string. Any downstream code expecting PostData to be null or a string would silently receive a JArray. Fix: extend the check to cover JArray by casting to JToken before calling .ToString(), which preserves the existing indented-string formatting. Regression test: Deserialize_RequestInfoArrayPostData_ConvertsToString confirms the fix — the test fails before the fix, passes after. Also move every '/* lang=json */' comment from inline (= /* lang=json */ "...") to its own line above the const declaration per project style convention. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 73b42af commit 2b9a0c5

25 files changed

Lines changed: 2324 additions & 2 deletions

src/Exceptionless/Serializer/RequestInfoConverter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
1616
if (result == null)
1717
return obj;
1818

19-
if (result.PostData is JObject)
20-
result.PostData = result.PostData.ToString();
19+
if (result.PostData is JObject || result.PostData is JArray)
20+
result.PostData = ((JToken)result.PostData).ToString();
2121

2222
return result;
2323
}

test/Exceptionless.Tests/Exceptionless.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<OutputType>Exe</OutputType>
1515
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
1616
<AssemblyName>Exceptionless.Tests</AssemblyName>
17+
<LangVersion>11.0</LangVersion>
1718
</PropertyGroup>
1819

1920
<ItemGroup>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using Exceptionless.Models;
2+
using Exceptionless.Tests.Serializer;
3+
using Xunit;
4+
5+
namespace Exceptionless.Tests.Serializer.Models {
6+
public class ClientConfigurationSerializerTests : SerializerTestBase {
7+
/* lang=json */
8+
private const string MinimalJson = """{"version":0,"settings":{}}""";
9+
/* lang=json */
10+
private const string CompleteJson = """{"version":1,"settings":{"@@log:*":"Off"}}""";
11+
12+
[Fact]
13+
public void Serialize_MinimalClientConfiguration_ProducesCorrectJson() {
14+
// Arrange
15+
var configuration = new ClientConfiguration();
16+
17+
// Act
18+
string json = Serialize(configuration);
19+
20+
// Assert
21+
Assert.Equal(MinimalJson, json);
22+
}
23+
24+
[Fact]
25+
public void Serialize_CompleteClientConfiguration_ProducesCorrectJson() {
26+
// Arrange
27+
var configuration = new ClientConfiguration { Version = 1 };
28+
configuration.Settings[SettingsDictionary.KnownKeys.LogLevelPrefix + "*"] = "Off";
29+
30+
// Act
31+
string json = Serialize(configuration);
32+
33+
// Assert
34+
Assert.Equal(CompleteJson, json);
35+
}
36+
37+
[Fact]
38+
public void Deserialize_ClientConfiguration_RoundTrips() {
39+
// Arrange
40+
var configuration = new ClientConfiguration { Version = 1 };
41+
configuration.Settings[SettingsDictionary.KnownKeys.LogLevelPrefix + "*"] = "Off";
42+
43+
// Act
44+
ClientConfiguration roundTripped = RoundTrip(configuration);
45+
46+
// Assert
47+
Assert.Equal(1, roundTripped.Version);
48+
Assert.Equal("Off", roundTripped.Settings["@@log:*"]);
49+
}
50+
51+
[Fact]
52+
public void Deserialize_ClientConfiguration_FromKnownJson_MapsAllProperties() {
53+
// Arrange
54+
const string json = CompleteJson;
55+
56+
// Act
57+
ClientConfiguration configuration = Deserialize<ClientConfiguration>(json);
58+
59+
// Assert
60+
Assert.Equal(1, configuration.Version);
61+
Assert.Single(configuration.Settings);
62+
Assert.Equal("Off", configuration.Settings["@@log:*"]);
63+
}
64+
}
65+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
using System;
2+
using Exceptionless.Models;
3+
using Exceptionless.Tests.Serializer;
4+
using Xunit;
5+
6+
namespace Exceptionless.Tests.Serializer.Models {
7+
public class DataDictionarySerializerTests : SerializerTestBase {
8+
/* lang=json */
9+
private const string MinimalJson = """{}""";
10+
/* lang=json */
11+
private const string CompleteJson = """{"string_value":"hello","int_value":42,"bool_value":true,"decimal_value":3.14,"null_value":null}""";
12+
/* lang=json */
13+
private const string KnownJson = """{"MyKey":"value","Count":42,"IsActive":true}""";
14+
/* lang=json */
15+
private const string NestedObjectJson = """{"nested":{"key":"val"}}""";
16+
/* lang=json */
17+
private const string NestedArrayJson = """{"items":["a","b"]}""";
18+
19+
[Fact]
20+
public void Serialize_MinimalDataDictionary_ProducesCorrectJson() {
21+
// Arrange
22+
var data = new DataDictionary();
23+
24+
// Act
25+
string json = Serialize(data);
26+
27+
// Assert
28+
Assert.Equal(MinimalJson, json);
29+
}
30+
31+
[Fact]
32+
public void Serialize_CompleteDataDictionary_ProducesCorrectJson() {
33+
// Arrange
34+
var data = new DataDictionary {
35+
["string_value"] = "hello",
36+
["int_value"] = 42,
37+
["bool_value"] = true,
38+
["decimal_value"] = 3.14m,
39+
["null_value"] = null
40+
};
41+
42+
// Act
43+
string json = Serialize(data);
44+
45+
// Assert
46+
Assert.Equal(CompleteJson, json);
47+
}
48+
49+
[Fact]
50+
public void Deserialize_DataDictionary_RoundTrips() {
51+
// Arrange
52+
var data = new DataDictionary {
53+
["string_value"] = "hello",
54+
["int_value"] = 42,
55+
["bool_value"] = true,
56+
["decimal_value"] = 3.14m,
57+
["null_value"] = null
58+
};
59+
60+
// Act
61+
DataDictionary roundTripped = RoundTrip(data);
62+
63+
// Assert
64+
Assert.Equal("hello", roundTripped["string_value"]);
65+
Assert.Equal(42L, Convert.ToInt64(roundTripped["int_value"]));
66+
Assert.True((bool)roundTripped["bool_value"]);
67+
Assert.Equal(3.14m, (decimal)roundTripped["decimal_value"]);
68+
Assert.Null(roundTripped["null_value"]);
69+
}
70+
71+
[Fact]
72+
public void Deserialize_DataDictionary_FromKnownJson_MapsAllProperties() {
73+
// Arrange
74+
const string json = KnownJson;
75+
76+
// Act
77+
DataDictionary data = Deserialize<DataDictionary>(json);
78+
79+
// Assert
80+
Assert.Equal("value", data["MyKey"]);
81+
Assert.Equal(42L, Convert.ToInt64(data["Count"]));
82+
Assert.True((bool)data["IsActive"]);
83+
}
84+
85+
[Fact]
86+
public void Deserialize_DataDictionary_JObjectValuesBecomeCompactStrings() {
87+
// Arrange
88+
const string json = NestedObjectJson;
89+
const string expected = /* lang=json */ """{"key":"val"}""";
90+
91+
// Act
92+
DataDictionary data = Deserialize<DataDictionary>(json);
93+
94+
// Assert
95+
Assert.Equal(expected, data["nested"]);
96+
}
97+
98+
[Fact]
99+
public void Deserialize_DataDictionary_JArrayValuesBecomeCompactStrings() {
100+
// Arrange
101+
const string json = NestedArrayJson;
102+
const string expected = /* lang=json */ """["a","b"]""";
103+
104+
// Act
105+
DataDictionary data = Deserialize<DataDictionary>(json);
106+
107+
// Assert
108+
Assert.Equal(expected, data["items"]);
109+
}
110+
}
111+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
using Exceptionless.Models.Data;
2+
using Exceptionless.Tests.Serializer;
3+
using Xunit;
4+
5+
namespace Exceptionless.Tests.Serializer.Models {
6+
public class EnvironmentInfoSerializerTests : SerializerTestBase {
7+
/* lang=json */
8+
private const string MinimalJson = """{"processor_count":0,"total_physical_memory":0,"available_physical_memory":0,"command_line":null,"process_name":null,"process_id":null,"process_memory_size":0,"thread_name":null,"thread_id":null,"architecture":null,"o_s_name":null,"o_s_version":null,"ip_address":null,"machine_name":null,"install_id":null,"runtime_version":null,"data":{}}""";
9+
/* lang=json */
10+
private const string CompleteJson = """{"processor_count":4,"total_physical_memory":8192,"available_physical_memory":4096,"command_line":"TestCommandLine","process_name":"TestProcess","process_id":"12345","process_memory_size":2048,"thread_name":"Thread","thread_id":"67890","architecture":"x64","o_s_name":"Windows","o_s_version":"10.0.19042","ip_address":"192.168.1.1","machine_name":"Machine","install_id":"InstallId","runtime_version":"5.0.0","data":{"FrameworkDescription":"Test"}}""";
11+
/* lang=json */
12+
private const string OsOnlyJson = """{"processor_count":0,"total_physical_memory":0,"available_physical_memory":0,"command_line":null,"process_name":null,"process_id":null,"process_memory_size":0,"thread_name":null,"thread_id":null,"architecture":null,"o_s_name":"Windows","o_s_version":"11.0","ip_address":null,"machine_name":null,"install_id":null,"runtime_version":null,"data":{}}""";
13+
14+
[Fact]
15+
public void Serialize_MinimalEnvironmentInfo_ProducesCorrectJson() {
16+
// Arrange
17+
var environmentInfo = new EnvironmentInfo();
18+
19+
// Act
20+
string json = Serialize(environmentInfo);
21+
22+
// Assert
23+
Assert.Equal(MinimalJson, json);
24+
}
25+
26+
[Fact]
27+
public void Serialize_CompleteEnvironmentInfo_ProducesCorrectJson() {
28+
// Arrange
29+
var environmentInfo = CreateCompleteEnvironmentInfo();
30+
31+
// Act
32+
string json = Serialize(environmentInfo);
33+
34+
// Assert
35+
Assert.Equal(CompleteJson, json);
36+
}
37+
38+
[Fact]
39+
public void Deserialize_EnvironmentInfo_RoundTrips() {
40+
// Arrange
41+
var environmentInfo = CreateCompleteEnvironmentInfo();
42+
43+
// Act
44+
EnvironmentInfo roundTripped = RoundTrip(environmentInfo);
45+
46+
// Assert
47+
Assert.Equal(4, roundTripped.ProcessorCount);
48+
Assert.Equal(8192, roundTripped.TotalPhysicalMemory);
49+
Assert.Equal(4096, roundTripped.AvailablePhysicalMemory);
50+
Assert.Equal("TestCommandLine", roundTripped.CommandLine);
51+
Assert.Equal("TestProcess", roundTripped.ProcessName);
52+
Assert.Equal("12345", roundTripped.ProcessId);
53+
Assert.Equal(2048, roundTripped.ProcessMemorySize);
54+
Assert.Equal("Thread", roundTripped.ThreadName);
55+
Assert.Equal("67890", roundTripped.ThreadId);
56+
Assert.Equal("x64", roundTripped.Architecture);
57+
Assert.Equal("Windows", roundTripped.OSName);
58+
Assert.Equal("10.0.19042", roundTripped.OSVersion);
59+
Assert.Equal("192.168.1.1", roundTripped.IpAddress);
60+
Assert.Equal("Machine", roundTripped.MachineName);
61+
Assert.Equal("InstallId", roundTripped.InstallId);
62+
Assert.Equal("5.0.0", roundTripped.RuntimeVersion);
63+
Assert.Equal("Test", roundTripped.Data["FrameworkDescription"]);
64+
}
65+
66+
[Fact]
67+
public void Deserialize_EnvironmentInfo_FromKnownJson_MapsAllProperties() {
68+
// Arrange
69+
const string json = CompleteJson;
70+
71+
// Act
72+
EnvironmentInfo environmentInfo = Deserialize<EnvironmentInfo>(json);
73+
74+
// Assert
75+
Assert.Equal(4, environmentInfo.ProcessorCount);
76+
Assert.Equal(8192, environmentInfo.TotalPhysicalMemory);
77+
Assert.Equal(4096, environmentInfo.AvailablePhysicalMemory);
78+
Assert.Equal("TestCommandLine", environmentInfo.CommandLine);
79+
Assert.Equal("TestProcess", environmentInfo.ProcessName);
80+
Assert.Equal("12345", environmentInfo.ProcessId);
81+
Assert.Equal(2048, environmentInfo.ProcessMemorySize);
82+
Assert.Equal("Thread", environmentInfo.ThreadName);
83+
Assert.Equal("67890", environmentInfo.ThreadId);
84+
Assert.Equal("x64", environmentInfo.Architecture);
85+
Assert.Equal("Windows", environmentInfo.OSName);
86+
Assert.Equal("10.0.19042", environmentInfo.OSVersion);
87+
Assert.Equal("192.168.1.1", environmentInfo.IpAddress);
88+
Assert.Equal("Machine", environmentInfo.MachineName);
89+
Assert.Equal("InstallId", environmentInfo.InstallId);
90+
Assert.Equal("5.0.0", environmentInfo.RuntimeVersion);
91+
Assert.Equal("Test", environmentInfo.Data["FrameworkDescription"]);
92+
}
93+
94+
[Fact]
95+
public void Serialize_EnvironmentInfo_UsesExplicitOsPropertyNames() {
96+
// Arrange
97+
var environmentInfo = new EnvironmentInfo {
98+
OSName = "Windows",
99+
OSVersion = "11.0"
100+
};
101+
102+
// Act
103+
string json = Serialize(environmentInfo);
104+
105+
// Assert
106+
Assert.Equal(OsOnlyJson, json);
107+
}
108+
109+
private static EnvironmentInfo CreateCompleteEnvironmentInfo() {
110+
return new EnvironmentInfo {
111+
ProcessorCount = 4,
112+
TotalPhysicalMemory = 8192,
113+
AvailablePhysicalMemory = 4096,
114+
CommandLine = "TestCommandLine",
115+
ProcessName = "TestProcess",
116+
ProcessId = "12345",
117+
ProcessMemorySize = 2048,
118+
ThreadName = "Thread",
119+
ThreadId = "67890",
120+
Architecture = "x64",
121+
OSName = "Windows",
122+
OSVersion = "10.0.19042",
123+
IpAddress = "192.168.1.1",
124+
MachineName = "Machine",
125+
InstallId = "InstallId",
126+
RuntimeVersion = "5.0.0",
127+
Data = {
128+
["FrameworkDescription"] = "Test"
129+
}
130+
};
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)