avrotize 2.20.3__py3-none-any.whl → 2.20.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
avrotize/__init__.py CHANGED
@@ -56,6 +56,8 @@ _mappings = {
56
56
  "convert_avro_to_rust": (f"{mod}.avrotorust", "convert_avro_to_rust"),
57
57
  "convert_avro_schema_to_rust": (f"{mod}.avrotorust", "convert_avro_schema_to_rust"),
58
58
  "convert_avro_to_datapackage": (f"{mod}.avrotodatapackage", "convert_avro_to_datapackage"),
59
+ "convert_structure_to_javascript": (f"{mod}.structuretojs", "convert_structure_to_javascript"),
60
+ "convert_structure_schema_to_javascript": (f"{mod}.structuretojs", "convert_structure_schema_to_javascript"),
59
61
  }
60
62
 
61
63
  _lazy_loader = LazyLoader(_mappings)
avrotize/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '2.20.3'
32
- __version_tuple__ = version_tuple = (2, 20, 3)
31
+ __version__ = version = '2.20.4'
32
+ __version_tuple__ = version_tuple = (2, 20, 4)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -0,0 +1,166 @@
1
+ # {{ project_name }}
2
+
3
+ This is a generated C# project from an Avro schema using [avrotize](https://github.com/clemensv/avrotize).
4
+
5
+ ## Project Structure
6
+
7
+ ```
8
+ {{ project_name }}/
9
+ ├── src/ # Generated C# classes from Avro schema
10
+ │ └── {{ project_name | pascal }}.csproj
11
+ ├── test/ # Generated unit tests
12
+ │ └── {{ project_name | pascal }}.Test.csproj
13
+ ├── run_coverage.ps1 # PowerShell coverage script
14
+ ├── run_coverage.sh # Bash coverage script (Linux/macOS)
15
+ └── {{ project_name }}.sln # Visual Studio solution file
16
+ ```
17
+
18
+ ## Building and Running
19
+
20
+ ### Quick Start
21
+ ```bash
22
+ # Build the project
23
+ dotnet build
24
+
25
+ # Run tests
26
+ dotnet test
27
+
28
+ # Run tests with coverage
29
+ dotnet test --collect:"XPlat Code Coverage"
30
+ ```
31
+
32
+ ### Code Coverage
33
+
34
+ This project includes automated code coverage collection and reporting.
35
+
36
+ #### Using the Coverage Scripts
37
+
38
+ **PowerShell (Windows):**
39
+ ```powershell
40
+ # Run tests with default 85% coverage threshold
41
+ .\run_coverage.ps1
42
+
43
+ # Set custom coverage threshold
44
+ .\run_coverage.ps1 -Threshold 90
45
+
46
+ # Generate HTML coverage report
47
+ .\run_coverage.ps1 -HtmlReport
48
+
49
+ # Generate and open HTML report in browser
50
+ .\run_coverage.ps1 -HtmlReport -OpenReport
51
+
52
+ # Run without failing on low coverage
53
+ .\run_coverage.ps1 -FailOnLowCoverage:$false
54
+ ```
55
+
56
+ **Bash (Linux/macOS):**
57
+ ```bash
58
+ # Run tests with default 85% coverage threshold
59
+ ./run_coverage.sh
60
+
61
+ # Set custom coverage threshold
62
+ ./run_coverage.sh --threshold 90
63
+
64
+ # Generate HTML coverage report
65
+ ./run_coverage.sh --html-report
66
+
67
+ # Generate and open HTML report in browser
68
+ ./run_coverage.sh --open-report
69
+
70
+ # Run without failing on low coverage
71
+ ./run_coverage.sh --no-fail
72
+ ```
73
+
74
+ #### Coverage Configuration
75
+
76
+ The test project is configured with the following coverage settings in `test/{{ project_name | pascal }}.Test.csproj`:
77
+
78
+ - **Default Threshold**: 85% line coverage
79
+ - **Output Format**: Cobertura XML
80
+ - **Coverage Output**: `./coverage/` directory
81
+ - **Includes**: All `{{ project_name | pascal }}` namespace code
82
+ - **Excludes**: Test projects
83
+
84
+ #### Coverage Reports
85
+
86
+ Coverage reports are generated in the following formats:
87
+
88
+ 1. **Cobertura XML**: `./coverage/[guid]/coverage.cobertura.xml`
89
+ 2. **HTML Report** (when requested): `./coverage/html/index.html`
90
+
91
+ #### Continuous Integration
92
+
93
+ For CI/CD pipelines, use:
94
+
95
+ ```yaml
96
+ # Example GitHub Actions step
97
+ - name: Run tests with coverage
98
+ run: |
99
+ dotnet test --collect:"XPlat Code Coverage" --results-directory:"./coverage"
100
+
101
+ # Or use the coverage script for threshold enforcement
102
+ - name: Run tests with coverage threshold
103
+ run: ./run_coverage.sh --threshold 85
104
+ ```
105
+
106
+ ## Generated Code Features
107
+
108
+ This project includes generated C# classes with the following features:
109
+
110
+ - **Value Equality**: Generated `Equals()` and `GetHashCode()` methods
111
+ - **Comprehensive Unit Tests**: Property tests, equality tests, and serialization tests
112
+ - **Multiple Serialization Support**: JSON, XML, and Avro serialization (if enabled)
113
+ - **Nullable Reference Types**: Full C# nullable reference type support
114
+
115
+ ## Test Coverage Goals
116
+
117
+ The generated tests aim to provide comprehensive coverage of:
118
+
119
+ - ✅ **Property Access**: All public properties
120
+ - ✅ **Equality Methods**: `Equals()` and `GetHashCode()`
121
+ - ✅ **Constructors**: Default and parameterized constructors
122
+ - ✅ **Serialization**: JSON/XML/Avro serialization (when enabled)
123
+ - ✅ **Edge Cases**: Null handling and type conversion
124
+
125
+ ### Typical Coverage Results
126
+
127
+ The generated tests typically achieve:
128
+ - **Line Coverage**: 95%+
129
+ - **Branch Coverage**: 90%+
130
+
131
+ ## Customizing Coverage
132
+
133
+ To modify coverage settings, edit the `test/{{ project_name | pascal }}.Test.csproj` file:
134
+
135
+ ```xml
136
+ <PropertyGroup>
137
+ <!-- Modify these values -->
138
+ <Threshold>85</Threshold> <!-- Minimum coverage % -->
139
+ <ThresholdType>line</ThresholdType> <!-- line, branch, or method -->
140
+ <ThresholdStat>minimum</ThresholdStat> <!-- minimum, average, or total -->
141
+ </PropertyGroup>
142
+ ```
143
+
144
+ ## Dependencies
145
+
146
+ ### Runtime
147
+ - .NET 9.0
148
+ - Newtonsoft.Json (if JSON serialization enabled)
149
+ - Apache.Avro (if Avro serialization enabled)
150
+
151
+ ### Testing
152
+ - NUnit 4.3.2
153
+ - NUnit3TestAdapter 5.0.0
154
+ - Microsoft.NET.Test.Sdk 17.13.0
155
+ - coverlet.collector 6.0.4
156
+ - coverlet.msbuild 6.0.4
157
+
158
+ ## Additional Tools
159
+
160
+ For enhanced coverage reporting, install ReportGenerator globally:
161
+
162
+ ```bash
163
+ dotnet tool install -g dotnet-reportgenerator-globaltool
164
+ ```
165
+
166
+ This enables HTML report generation with the `-HtmlReport` option.
@@ -122,6 +122,17 @@ public class {{ test_class_name }}
122
122
  Assert.That(newInstance, Is.EqualTo(_instance));
123
123
  }
124
124
  {%- endif %}
125
+ {%- if protobuf_net_annotation %}
126
+ /// <summary> Testing Protobuf serializer </summary>
127
+ [Test]
128
+ public void Test_ToByteArray_FromData_Protobuf()
129
+ {
130
+ var mediaType = "application/x-protobuf";
131
+ var bytes = _instance.ToByteArray(mediaType);
132
+ var newInstance = {{ class_base_name }}.FromData(bytes, mediaType);
133
+ Assert.That(newInstance, Is.EqualTo(_instance));
134
+ }
135
+ {%- endif %}
125
136
  {%- if system_xml_annotation %}
126
137
  /// <summary> Testing XML serializer </summary>
127
138
  [Test]
@@ -133,6 +144,121 @@ public class {{ test_class_name }}
133
144
  Assert.That(newInstance, Is.EqualTo(_instance));
134
145
  }
135
146
  {%- endif %}
147
+ {%- if system_text_json_annotation %}
148
+ /// <summary> Testing JSON serializer </summary>
149
+ [Test]
150
+ public void Test_ToByteArray_FromData_Json()
151
+ {
152
+ var mediaType = "application/json";
153
+ var bytes = _instance.ToByteArray(mediaType);
154
+ var newInstance = {{ class_base_name }}.FromData(bytes, mediaType);
155
+ Assert.That(newInstance, Is.EqualTo(_instance));
156
+ }
157
+ {%- endif %}
158
+ {%- if msgpack_annotation %}
159
+ /// <summary> Testing MsgPack serializer </summary>
160
+ [Test]
161
+ public void Test_ToByteArray_FromData_MsgPack()
162
+ {
163
+ var mediaType = "application/msgpack";
164
+ var bytes = _instance.ToByteArray(mediaType);
165
+ var newInstance = {{ class_base_name }}.FromData(bytes, mediaType);
166
+ Assert.That(newInstance, Is.EqualTo(_instance));
167
+ }
168
+ {%- endif %}
169
+ {%- if avro_annotation or system_text_json_annotation or newtonsoft_json_annotation or system_xml_annotation or msgpack_annotation or protobuf_net_annotation %}
170
+
171
+ /// <summary> Testing FromData with edge cases </summary>
172
+ [Test]
173
+ public void Test_FromData_EdgeCases()
174
+ {
175
+ {%- if system_text_json_annotation %}
176
+ // Test null data - FromData returns null for null input
177
+ Assert.That({{ class_base_name }}.FromData((byte[])null!, "application/json"), Is.Null);
178
+ // Test empty data - should throw JsonException for invalid JSON
179
+ Assert.Throws<System.Text.Json.JsonException>(() => {{ class_base_name }}.FromData(Array.Empty<byte>(), "application/json"));
180
+ {%- elif newtonsoft_json_annotation %}
181
+ // Test null data - FromData returns null for null input
182
+ Assert.That({{ class_base_name }}.FromData((byte[])null!, "application/json"), Is.Null);
183
+ // Test empty data - Newtonsoft.Json returns null for empty string
184
+ Assert.That({{ class_base_name }}.FromData(Array.Empty<byte>(), "application/json"), Is.Null);
185
+ {%- elif avro_annotation %}
186
+ // Test null data - FromData returns null for null input
187
+ Assert.That({{ class_base_name }}.FromData((byte[])null!, "avro/binary"), Is.Null);
188
+ {%- elif msgpack_annotation %}
189
+ // Test null data - FromData returns null for null input
190
+ Assert.That({{ class_base_name }}.FromData((byte[])null!, "application/msgpack"), Is.Null);
191
+ {%- elif protobuf_net_annotation %}
192
+ // Test null data - FromData returns null for null input
193
+ Assert.That({{ class_base_name }}.FromData((byte[])null!, "application/x-protobuf"), Is.Null);
194
+ {%- elif system_xml_annotation %}
195
+ // Test null data - FromData returns null for null input
196
+ Assert.That({{ class_base_name }}.FromData((byte[])null!, "application/xml"), Is.Null);
197
+ {%- endif %}
198
+ }
199
+ {%- endif %}
200
+ {%- if system_text_json_annotation %}
201
+
202
+ /// <summary> Testing IsJsonMatch </summary>
203
+ [Test]
204
+ public void Test_IsJsonMatch()
205
+ {
206
+ var json = System.Text.Json.JsonSerializer.Serialize(_instance);
207
+ using var doc = System.Text.Json.JsonDocument.Parse(json);
208
+ Assert.That({{ class_base_name }}.IsJsonMatch(doc.RootElement), Is.True);
209
+ }
210
+ {%- endif %}
211
+ {%- if system_text_json_annotation or newtonsoft_json_annotation %}
212
+
213
+ /// <summary> Testing Gzip compression </summary>
214
+ [Test]
215
+ public void Test_ToByteArray_Gzip()
216
+ {
217
+ var mediaType = "application/json";
218
+ var gzipMediaType = "application/json+gzip";
219
+ var plainBytes = _instance.ToByteArray(mediaType);
220
+ var gzipBytes = _instance.ToByteArray(gzipMediaType);
221
+ // Gzip compressed data should start with magic bytes 0x1f 0x8b
222
+ Assert.That(gzipBytes.Length >= 2 && gzipBytes[0] == 0x1f && gzipBytes[1] == 0x8b, Is.True,
223
+ "Gzip compressed data should have correct magic bytes");
224
+ var newInstance = {{ class_base_name }}.FromData(gzipBytes, gzipMediaType);
225
+ Assert.That(newInstance, Is.EqualTo(_instance));
226
+ }
227
+ {%- endif %}
228
+ {%- if msgpack_annotation and not (system_text_json_annotation or newtonsoft_json_annotation) %}
229
+
230
+ /// <summary> Testing Gzip compression with MessagePack </summary>
231
+ [Test]
232
+ public void Test_ToByteArray_Gzip_MsgPack()
233
+ {
234
+ var mediaType = "application/msgpack";
235
+ var gzipMediaType = "application/msgpack+gzip";
236
+ var plainBytes = _instance.ToByteArray(mediaType);
237
+ var gzipBytes = _instance.ToByteArray(gzipMediaType);
238
+ // Gzip compressed data should start with magic bytes 0x1f 0x8b
239
+ Assert.That(gzipBytes.Length >= 2 && gzipBytes[0] == 0x1f && gzipBytes[1] == 0x8b, Is.True,
240
+ "Gzip compressed data should have correct magic bytes");
241
+ var newInstance = {{ class_base_name }}.FromData(gzipBytes, gzipMediaType);
242
+ Assert.That(newInstance, Is.EqualTo(_instance));
243
+ }
244
+ {%- endif %}
245
+ {%- if protobuf_net_annotation and not (system_text_json_annotation or newtonsoft_json_annotation or msgpack_annotation) %}
246
+
247
+ /// <summary> Testing Gzip compression with Protobuf </summary>
248
+ [Test]
249
+ public void Test_ToByteArray_Gzip_Protobuf()
250
+ {
251
+ var mediaType = "application/x-protobuf";
252
+ var gzipMediaType = "application/x-protobuf+gzip";
253
+ var plainBytes = _instance.ToByteArray(mediaType);
254
+ var gzipBytes = _instance.ToByteArray(gzipMediaType);
255
+ // Gzip compressed data should start with magic bytes 0x1f 0x8b
256
+ Assert.That(gzipBytes.Length >= 2 && gzipBytes[0] == 0x1f && gzipBytes[1] == 0x8b, Is.True,
257
+ "Gzip compressed data should have correct magic bytes");
258
+ var newInstance = {{ class_base_name }}.FromData(gzipBytes, gzipMediaType);
259
+ Assert.That(newInstance, Is.EqualTo(_instance));
260
+ }
261
+ {%- endif %}
136
262
  }
137
263
  {% endfilter %}
138
264
  {% if namespace %}
@@ -1,4 +1,4 @@
1
- {%- if avro_annotation or system_text_json_annotation or newtonsoft_json_annotation or system_xml_annotation %}
1
+ {%- if avro_annotation or system_text_json_annotation or newtonsoft_json_annotation or system_xml_annotation or protobuf_net_annotation or msgpack_annotation %}
2
2
  /// <summary>
3
3
  /// Creates an object from the data
4
4
  /// </summary>
@@ -11,7 +11,7 @@
11
11
  if ( data is {{ class_name }}) return ({{ class_name }})data;
12
12
  if ( contentTypeString == null ) contentTypeString = System.Net.Mime.MediaTypeNames.Application.Octet;
13
13
  var contentType = new System.Net.Mime.ContentType(contentTypeString);
14
- {%- if avro_annotation or system_text_json_annotation or newtonsoft_json_annotation or system_xml_annotation %}
14
+ {%- if avro_annotation or system_text_json_annotation or newtonsoft_json_annotation or system_xml_annotation or protobuf_net_annotation or msgpack_annotation %}
15
15
  if ( contentType.MediaType.EndsWith("+gzip"))
16
16
  {
17
17
  var stream = data switch
@@ -84,10 +84,32 @@
84
84
  }
85
85
  else if (data is System.BinaryData)
86
86
  {
87
- return ((System.BinaryData)data).ToObjectFromJson<{{ class_name }}>();
87
+ return Newtonsoft.Json.JsonConvert.DeserializeObject<{{ class_name }}>(((System.BinaryData)data).ToString());
88
+ }
89
+ else if (data is byte[])
90
+ {
91
+ return Newtonsoft.Json.JsonConvert.DeserializeObject<{{ class_name }}>(System.Text.Encoding.UTF8.GetString((byte[])data));
92
+ }
93
+ else if (data is System.IO.Stream)
94
+ {
95
+ using (var reader = new System.IO.StreamReader((System.IO.Stream)data))
96
+ {
97
+ return Newtonsoft.Json.JsonConvert.DeserializeObject<{{ class_name }}>(reader.ReadToEnd());
98
+ }
88
99
  }
89
100
  }
90
101
  {%- endif %}
102
+ {%- if protobuf_net_annotation %}
103
+ if ( contentType.MediaType.StartsWith("application/x-protobuf") || contentType.MediaType.StartsWith("application/protobuf") || contentType.MediaType.StartsWith("application/vnd.google.protobuf"))
104
+ {
105
+ var stream = data switch
106
+ {
107
+ System.IO.Stream s => s, System.BinaryData bd => bd.ToStream(), byte[] bytes => new System.IO.MemoryStream(bytes),
108
+ _ => throw new NotSupportedException("Data is not of a supported type for conversion to Stream")
109
+ };
110
+ return global::ProtoBuf.Serializer.Deserialize<{{ class_name }}>(stream);
111
+ }
112
+ {%- endif %}
91
113
  {%- if system_xml_annotation %}
92
114
  if ( contentType.MediaType.StartsWith(System.Net.Mime.MediaTypeNames.Text.Xml) || contentType.MediaType.StartsWith(System.Net.Mime.MediaTypeNames.Application.Xml) || contentType.MediaType.EndsWith("+xml"))
93
115
  {
@@ -114,11 +136,35 @@
114
136
  return ({{ class_name }}?)serializer.Deserialize(memoryStream);
115
137
  }
116
138
  }
139
+ {%- endif %}
140
+ {%- if msgpack_annotation %}
141
+ if (contentType.MediaType.StartsWith("application/msgpack") || contentType.MediaType.StartsWith("application/x-msgpack"))
142
+ {
143
+ var bytes = data switch
144
+ {
145
+ byte[] b => b,
146
+ System.IO.Stream s => ReadStreamToBytes(s),
147
+ System.BinaryData bd => bd.ToArray(),
148
+ _ => throw new NotSupportedException("Data type not supported for MsgPack")
149
+ };
150
+ return MessagePack.MessagePackSerializer.Deserialize<{{ class_name }}>(bytes);
151
+ }
117
152
  {%- endif %}
118
153
  throw new System.NotSupportedException($"Unsupported media type {contentType.MediaType}");
119
154
  }
120
155
  {%- endif %}
121
156
 
157
+ {%- if msgpack_annotation %}
158
+ private static byte[] ReadStreamToBytes(System.IO.Stream stream)
159
+ {
160
+ using (var memoryStream = new System.IO.MemoryStream())
161
+ {
162
+ stream.CopyTo(memoryStream);
163
+ return memoryStream.ToArray();
164
+ }
165
+ }
166
+ {%- endif %}
167
+
122
168
  {%- if avro_annotation %}
123
169
  private class SpecificDatumWriter : global::Avro.Specific.SpecificDatumWriter<{{ class_name }}>
124
170
  {
@@ -144,7 +190,7 @@
144
190
  {%- if avro_annotation %}
145
191
  {%- endif%}
146
192
 
147
- {%- if avro_annotation or system_text_json_annotation or newtonsoft_json_annotation or system_xml_annotation %}
193
+ {%- if avro_annotation or system_text_json_annotation or newtonsoft_json_annotation or system_xml_annotation or protobuf_net_annotation or msgpack_annotation %}
148
194
  /// <summary>
149
195
  /// Converts the object to a byte array
150
196
  /// </summary>
@@ -201,7 +247,23 @@
201
247
  }
202
248
  }
203
249
  {%- endif %}
204
- {%- if avro_annotation or system_text_json_annotation or newtonsoft_json_annotation or system_xml_annotation %}
250
+ {%- if protobuf_net_annotation %}
251
+ if (contentType.MediaType.StartsWith("application/x-protobuf") || contentType.MediaType.StartsWith("application/protobuf") || contentType.MediaType.StartsWith("application/vnd.google.protobuf"))
252
+ {
253
+ using (var stream = new System.IO.MemoryStream())
254
+ {
255
+ global::ProtoBuf.Serializer.Serialize(stream, this);
256
+ result = stream.ToArray();
257
+ }
258
+ }
259
+ {%- endif %}
260
+ {%- if msgpack_annotation %}
261
+ if (contentType.MediaType.StartsWith("application/msgpack") || contentType.MediaType.StartsWith("application/x-msgpack"))
262
+ {
263
+ result = MessagePack.MessagePackSerializer.Serialize(this);
264
+ }
265
+ {%- endif %}
266
+ {%- if avro_annotation or system_text_json_annotation or newtonsoft_json_annotation or system_xml_annotation or protobuf_net_annotation or msgpack_annotation %}
205
267
  if (result != null && contentType.MediaType.EndsWith("+gzip"))
206
268
  {
207
269
  var stream = new System.IO.MemoryStream();
@@ -8,12 +8,18 @@
8
8
  {%- if avro_annotation %}
9
9
  <PackageReference Include="Apache.Avro" Version="{{ CSHARP_AVRO_VERSION }}" />
10
10
  {%- endif %}
11
+ {%- if protobuf_net_annotation %}
12
+ <PackageReference Include="protobuf-net" Version="{{ PROTOBUF_NET_VERSION }}" />
13
+ {%- endif %}
11
14
  {%- if newtonsoft_json_annotation %}
12
15
  <PackageReference Include="Newtonsoft.Json" Version="{{ NEWTONSOFT_JSON_VERSION }}" />
13
16
  {%- endif %}
14
17
  {%- if system_text_json_annotation %}
15
18
  <PackageReference Include="System.Text.Json" Version="{{ SYSTEM_TEXT_JSON_VERSION }}" />
16
19
  {%- endif %}
20
+ {%- if msgpack_annotation %}
21
+ <PackageReference Include="MessagePack" Version="{{ MSGPACK_VERSION }}" />
22
+ {%- endif %}
17
23
  <PackageReference Include="System.Memory.Data" Version="{{ SYSTEM_MEMORY_DATA_VERSION }}" />
18
24
  </ItemGroup>
19
25
  <ItemGroup>
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env pwsh
2
+ # PowerShell script to run tests with code coverage and generate reports
3
+ # Generated for project: {{ project_name }}
4
+
5
+ param(
6
+ [Parameter(HelpMessage="Minimum coverage threshold (default: 85)")]
7
+ [int]$Threshold = 85,
8
+
9
+ [Parameter(HelpMessage="Generate HTML report")]
10
+ [switch]$HtmlReport = $false,
11
+
12
+ [Parameter(HelpMessage="Open HTML report in browser")]
13
+ [switch]$OpenReport = $false,
14
+
15
+ [Parameter(HelpMessage="Fail build if coverage below threshold")]
16
+ [switch]$FailOnLowCoverage = $true
17
+ )
18
+
19
+ Write-Host "🧪 Running tests with code coverage for {{ project_name }}..." -ForegroundColor Green
20
+
21
+ # Clean previous coverage results
22
+ $coverageDir = "./coverage"
23
+ if (Test-Path $coverageDir) {
24
+ Remove-Item $coverageDir -Recurse -Force
25
+ Write-Host "🧹 Cleaned previous coverage results" -ForegroundColor Yellow
26
+ }
27
+
28
+ # Run tests with coverage collection
29
+ Write-Host "📊 Collecting code coverage..." -ForegroundColor Blue
30
+ $testResult = dotnet test --collect:"XPlat Code Coverage" --results-directory:"$coverageDir" --logger:"console;verbosity=detailed"
31
+ $testExitCode = $LASTEXITCODE
32
+
33
+ if ($testExitCode -ne 0) {
34
+ Write-Host "❌ Tests failed!" -ForegroundColor Red
35
+ exit $testExitCode
36
+ }
37
+
38
+ # Find coverage file
39
+ $coverageFile = Get-ChildItem -Recurse -Path $coverageDir -Name "*.cobertura.xml" | Select-Object -First 1
40
+ if (-not $coverageFile) {
41
+ Write-Host "❌ No coverage file found!" -ForegroundColor Red
42
+ exit 1
43
+ }
44
+
45
+ $coverageFilePath = Join-Path $coverageDir $coverageFile
46
+ Write-Host "📄 Coverage file: $coverageFilePath" -ForegroundColor Green
47
+
48
+ # Parse coverage percentage
49
+ $coverage = Select-Xml -Path $coverageFilePath -XPath "/coverage/@line-rate"
50
+ $coveragePercent = [math]::Round([double]$coverage.Node.Value * 100, 2)
51
+
52
+ Write-Host "📈 Line Coverage: $coveragePercent%" -ForegroundColor $(if ($coveragePercent -ge $Threshold) { "Green" } else { "Red" })
53
+
54
+ # Parse branch coverage
55
+ $branchCoverage = Select-Xml -Path $coverageFilePath -XPath "/coverage/@branch-rate"
56
+ $branchPercent = [math]::Round([double]$branchCoverage.Node.Value * 100, 2)
57
+ Write-Host "🌿 Branch Coverage: $branchPercent%" -ForegroundColor $(if ($branchPercent -ge $Threshold) { "Green" } else { "Red" })
58
+
59
+ # Generate HTML report if requested
60
+ if ($HtmlReport) {
61
+ Write-Host "📊 Generating HTML coverage report..." -ForegroundColor Blue
62
+
63
+ # Check if reportgenerator is installed
64
+ $reportGenExists = Get-Command reportgenerator -ErrorAction SilentlyContinue
65
+ if (-not $reportGenExists) {
66
+ Write-Host "⬇️ Installing ReportGenerator..." -ForegroundColor Yellow
67
+ dotnet tool install -g dotnet-reportgenerator-globaltool
68
+ }
69
+
70
+ $htmlReportPath = Join-Path $coverageDir "html"
71
+ reportgenerator "-reports:$coverageFilePath" "-targetdir:$htmlReportPath" "-reporttypes:Html"
72
+
73
+ Write-Host "📊 HTML report generated: $htmlReportPath/index.html" -ForegroundColor Green
74
+
75
+ if ($OpenReport) {
76
+ $indexPath = Join-Path $htmlReportPath "index.html"
77
+ Start-Process $indexPath
78
+ Write-Host "🌐 Opened coverage report in browser" -ForegroundColor Green
79
+ }
80
+ }
81
+
82
+ # Coverage summary
83
+ Write-Host ""
84
+ Write-Host "📊 Coverage Summary:" -ForegroundColor Cyan
85
+ Write-Host " Line Coverage: $coveragePercent% (Threshold: $Threshold%)" -ForegroundColor White
86
+ Write-Host " Branch Coverage: $branchPercent%" -ForegroundColor White
87
+ Write-Host " Status: $(if ($coveragePercent -ge $Threshold) { "✅ PASSED" } else { "❌ FAILED" })" -ForegroundColor $(if ($coveragePercent -ge $Threshold) { "Green" } else { "Red" })
88
+
89
+ # Fail if coverage is below threshold and FailOnLowCoverage is set
90
+ if ($FailOnLowCoverage -and $coveragePercent -lt $Threshold) {
91
+ Write-Host ""
92
+ Write-Host "❌ Coverage $coveragePercent% is below the required threshold of $Threshold%" -ForegroundColor Red
93
+ exit 1
94
+ }
95
+
96
+ Write-Host ""
97
+ Write-Host "✅ Coverage analysis completed successfully!" -ForegroundColor Green
98
+ exit 0