ci: add CI pipeline with build, test, coverage, and sticky PR comment #1

Merged
bjoern merged 1 commit from chore/add-ci-pipeline into main 2026-06-28 20:21:01 +02:00
Member

Summary

Ports the CI/CD pipeline from TeamAI/doujin-manager to OpenRouter.Net.

What the pipeline does

Runs on push to main and PRs targeting main:

Job Purpose
build dotnet restore + Release build — fast-fail gate
test (needs build) Runs all test projects with coverlet coverage → merges cobertura reports via reportgenerator → uploads coverage-report.zip artifact → posts sticky PR coverage comment

Sticky PR comment

Uses an HTML marker (<!-- coverage-comment -->) to find/update a single comment on each run, so the coverage summary evolves without spamming the thread. Built from ReportGenerator's MarkdownSummaryGithub output.

Adaptation from doujin-manager

The only structural difference: doujin-manager keeps its solution under backend/, while OpenRouter.Net.slnx lives at the repo root. So the working-directory: backend default was removed — everything else carries over verbatim.

No .csproj changes needed: all three test projects (OpenRouter.Net.Tests, OpenRouter.Net.Agents.Tests, OpenRouter.Net.Imaging.Tests) already reference coverlet.collector via CPM (Directory.Packages.props).

Verified locally

dotnet restore                     ✓
dotnet build -c Release            ✓  0 warnings, 0 errors
dotnet test  -c Release            ✓  135 passed, 0 failed
coverage.cobertura.xml             ✓  3 reports (one per test project)
reportgenerator merge              ✓  Cobertura + Html + MarkdownSummaryGithub

Current coverage baseline: 75.4% line / 64.5% branch (1358/1800 lines, 448/694 branches) across 3 assemblies, 105 classes.

Forgejo-specific notes

  • Artifact upload uses the Forgejo fork: https://code.forgejo.org/forgejo/upload-artifact@v4
  • Artifact named coverage-report.zip (Forgejo serves the name verbatim — no auto .zip appended)
  • PR comment posted via Gitea REST API (/issues/{pr}/comments — PRs share the issue comment namespace)
  • permissions: issues: write, pull-requests: write on the test job for the token to post comments
## Summary Ports the CI/CD pipeline from `TeamAI/doujin-manager` to `OpenRouter.Net`. ## What the pipeline does Runs on **push to `main`** and **PRs targeting `main`**: | Job | Purpose | |---|---| | **build** | `dotnet restore` + Release build — fast-fail gate | | **test** (needs build) | Runs all test projects with coverlet coverage → merges cobertura reports via reportgenerator → uploads `coverage-report.zip` artifact → posts sticky PR coverage comment | ### Sticky PR comment Uses an HTML marker (`<!-- coverage-comment -->`) to find/update a single comment on each run, so the coverage summary evolves without spamming the thread. Built from ReportGenerator's `MarkdownSummaryGithub` output. ## Adaptation from doujin-manager The only structural difference: `doujin-manager` keeps its solution under `backend/`, while `OpenRouter.Net.slnx` lives at the **repo root**. So the `working-directory: backend` default was removed — everything else carries over verbatim. No `.csproj` changes needed: all three test projects (`OpenRouter.Net.Tests`, `OpenRouter.Net.Agents.Tests`, `OpenRouter.Net.Imaging.Tests`) already reference `coverlet.collector` via CPM (`Directory.Packages.props`). ## Verified locally ``` dotnet restore ✓ dotnet build -c Release ✓ 0 warnings, 0 errors dotnet test -c Release ✓ 135 passed, 0 failed coverage.cobertura.xml ✓ 3 reports (one per test project) reportgenerator merge ✓ Cobertura + Html + MarkdownSummaryGithub ``` Current coverage baseline: **75.4% line / 64.5% branch** (1358/1800 lines, 448/694 branches) across 3 assemblies, 105 classes. ## Forgejo-specific notes - Artifact upload uses the Forgejo fork: `https://code.forgejo.org/forgejo/upload-artifact@v4` - Artifact named `coverage-report.zip` (Forgejo serves the name verbatim — no auto `.zip` appended) - PR comment posted via Gitea REST API (`/issues/{pr}/comments` — PRs share the issue comment namespace) - `permissions: issues: write, pull-requests: write` on the test job for the token to post comments
ci: add CI pipeline with build, test, coverage, and sticky PR comment
All checks were successful
CI / build (pull_request) Successful in 12s
CI / test (pull_request) Successful in 20s
a018eafc27
Ports the doujin-manager CI workflow to OpenRouter.Net. The pipeline runs
on pushes to main and on PRs targeting main:

- build job: restore + Release build (fast-fail gate)
- test job (needs build): runs all test projects with coverlet coverage,
  merges per-project cobertura reports via reportgenerator into unified
  Cobertura/Html/MarkdownSummary outputs, uploads as Forgejo artifact
  (coverage-report.zip), and posts a sticky PR comment with the coverage
  summary via the Forgejo REST API.

Adapted from TeamAI/doujin-manager: dropped the backend/ working-directory
since OpenRouter.Net.slnx lives at the repo root. All test projects
already reference coverlet.collector via CPM, so no csproj changes needed.

Verified locally: 135 tests pass, 3 cobertura XMLs produced, merged
report shows 75.4% line / 64.5% branch coverage.

Summary

Summary
Generated on: 06/28/2026 - 18:20:08
Coverage date: 06/28/2026 - 18:20:05 - 06/28/2026 - 18:20:06
Parser: MultiReport (3x Cobertura)
Assemblies: 3
Classes: 105
Files: 105
Line coverage: 75.4% (1358 of 1800)
Covered lines: 1358
Uncovered lines: 442
Coverable lines: 1800
Total lines: 5264
Branch coverage: 64.5% (448 of 694)
Covered branches: 448
Total branches: 694
Method coverage: Feature is only available for sponsors

Coverage

OpenRouter.Net - 68.8%
Name Line Branch
OpenRouter.Net 68.8% 64.3%
OpenRouter.Net.Client.CachingOpenRouterClient 72.4% 81.2%
OpenRouter.Net.Client.OpenRouterClient 85.4% 71.7%
OpenRouter.Net.Client.OpenRouterClientOptions 0%
OpenRouter.Net.Client.OpenRouterRequestLoggingHandler 27.2% 7.1%
OpenRouter.Net.Client.RetryOptions 80% 0%
OpenRouter.Net.Extensions.ServiceCollectionExtensions 0% 0%
OpenRouter.Net.Internal.ApiErrorEnvelope 100%
OpenRouter.Net.Internal.AssistantContent 92.8% 75%
OpenRouter.Net.Internal.AssistantContentJsonConverter 75% 64.2%
OpenRouter.Net.Internal.ContentPartListConverter 85.7% 66.6%
OpenRouter.Net.Internal.ErrorParser 69.2% 62.5%
OpenRouter.Net.Internal.JsonOptions 100%
OpenRouter.Net.Internal.ListEnvelope`1 100%
OpenRouter.Net.Internal.ObjectEnvelope`1 100%
OpenRouter.Net.Internal.RetryPolicy 90.9% 84.7%
OpenRouter.Net.Internal.SseEventReader 91.6% 83.3%
OpenRouter.Net.Internal.TolerantStringEnumConverter`1 90.1% 80%
OpenRouter.Net.Internal.ToolChoiceJsonConverter 61.7% 46.1%
OpenRouter.Net.Models.Common.ApiError 66.6%
OpenRouter.Net.Models.Common.CacheControl 100%
OpenRouter.Net.Models.Common.Result 100%
OpenRouter.Net.Models.Common.Result`1 60% 30%
OpenRouter.Net.Models.Content.AudioPart 100%
OpenRouter.Net.Models.Content.ImagePart 100%
OpenRouter.Net.Models.Content.ImageUrl 100%
OpenRouter.Net.Models.Content.InputAudio 100%
OpenRouter.Net.Models.Content.TextPart 100%
OpenRouter.Net.Models.Messages.AssistantMessage 100% 70%
OpenRouter.Net.Models.Messages.DeveloperMessage 0%
OpenRouter.Net.Models.Messages.SystemMessage 57.1%
OpenRouter.Net.Models.Messages.ToolMessage 54.5%
OpenRouter.Net.Models.Messages.UserMessage 100%
OpenRouter.Net.Models.Requests.ChatCompletionRequest 92.6% 58.3%
OpenRouter.Net.Models.Requests.FunctionCall 100%
OpenRouter.Net.Models.Requests.FunctionDefinition 75%
OpenRouter.Net.Models.Requests.JsonSchemaSpec 0%
OpenRouter.Net.Models.Requests.ProviderPreferences 0%
OpenRouter.Net.Models.Requests.ReasoningEncryptedDetail 100%
OpenRouter.Net.Models.Requests.ReasoningOptions 100%
OpenRouter.Net.Models.Requests.ReasoningSummaryDetail 100%
OpenRouter.Net.Models.Requests.ReasoningTextDetail 100%
OpenRouter.Net.Models.Requests.ResponseFormat 0%
OpenRouter.Net.Models.Requests.ToolCall 75%
OpenRouter.Net.Models.Requests.ToolChoice 100%
OpenRouter.Net.Models.Requests.ToolDefinition 100%
OpenRouter.Net.Models.Requests.UsageOptions 0%
OpenRouter.Net.Models.Responses.ChatCompletionChunk 44.4% 0%
OpenRouter.Net.Models.Responses.ChatCompletionResponse 80% 0%
OpenRouter.Net.Models.Responses.Choice 93.7% 100%
OpenRouter.Net.Models.Responses.CompletionTokensDetails 0%
OpenRouter.Net.Models.Responses.CreditsInfo 100%
OpenRouter.Net.Models.Responses.GenerationInfo 9%
OpenRouter.Net.Models.Responses.KeyInfo 0%
OpenRouter.Net.Models.Responses.ModelArchitecture 20%
OpenRouter.Net.Models.Responses.ModelEndpointsResponse 0%
OpenRouter.Net.Models.Responses.ModelInfo 20%
OpenRouter.Net.Models.Responses.ModelPricing 0%
OpenRouter.Net.Models.Responses.PromptTokensDetails 0%
OpenRouter.Net.Models.Responses.ProviderEndpoint 0%
OpenRouter.Net.Models.Responses.RateLimit 0%
OpenRouter.Net.Models.Responses.TopProvider 0%
OpenRouter.Net.Models.Responses.Usage 66.6%
OpenRouter.Net.OpenRouterException 83.3%
System.Text.RegularExpressions.Generated 83.3% 57.6%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FFC6B051A15CDCE5
43F954A3762D43C2CD5DCD5CDF29C19D3789DC49A7DCA1C47__Base64DataUriPattern_0
82% 58.3%
OpenRouter.Net.Agents - 81.7%
Name Line Branch
OpenRouter.Net.Agents 81.7% 65.3%
OpenRouter.Net.Agents.Agent 92.1% 77.8%
OpenRouter.Net.Agents.AgentCompletedEvent 100%
OpenRouter.Net.Agents.AgentEvent 100%
OpenRouter.Net.Agents.AgentOptions 100%
OpenRouter.Net.Agents.AgentResult 100% 50%
OpenRouter.Net.Agents.AgentSnapshot 100%
OpenRouter.Net.Agents.AssistantTurnEvent 100%
OpenRouter.Net.Agents.Extensions.ServiceCollectionExtensions 0%
OpenRouter.Net.Agents.RoundDetail 87.5%
OpenRouter.Net.Agents.RoundEndEvent 100%
OpenRouter.Net.Agents.ToolCallCompletedEvent 100%
OpenRouter.Net.Agents.ToolCallSkippedEvent 100%
OpenRouter.Net.Agents.ToolCallStartedEvent 100%
OpenRouter.Net.Agents.ToolExecutionDetail 100%
OpenRouter.Net.Agents.Tools.FileSystem.CopyParams 100%
OpenRouter.Net.Agents.Tools.FileSystem.CopyTool 42.1% 21.4%
OpenRouter.Net.Agents.Tools.FileSystem.DeleteParams 50%
OpenRouter.Net.Agents.Tools.FileSystem.DeleteTool 57.1% 50%
OpenRouter.Net.Agents.Tools.FileSystem.DiffParams 100%
OpenRouter.Net.Agents.Tools.FileSystem.DiffTool 83.3% 77.1%
OpenRouter.Net.Agents.Tools.FileSystem.ListDirectoryParams 100%
OpenRouter.Net.Agents.Tools.FileSystem.ListDirectoryTool 68% 61.1%
OpenRouter.Net.Agents.Tools.FileSystem.MoveParams 100%
OpenRouter.Net.Agents.Tools.FileSystem.MoveTool 55% 33.3%
OpenRouter.Net.Agents.Tools.FileSystem.ReadFileParams 100%
OpenRouter.Net.Agents.Tools.FileSystem.ReadFileTool 73.6% 60%
OpenRouter.Net.Agents.Tools.FileSystem.SearchParams 100%
OpenRouter.Net.Agents.Tools.FileSystem.SearchTool 67.5% 70.8%
OpenRouter.Net.Agents.Tools.FileSystem.WriteFileParams 100%
OpenRouter.Net.Agents.Tools.FileSystem.WriteFileTool 75% 62.5%
OpenRouter.Net.Agents.Tools.ITool 100%
OpenRouter.Net.Agents.Tools.SubAgent.SubAgentParams 0%
OpenRouter.Net.Agents.Tools.SubAgent.SubAgentTool 0% 0%
OpenRouter.Net.Agents.Tools.Tool`1 88.8% 50%
OpenRouter.Net.Agents.Tools.ToolInvocationContext 100%
OpenRouter.Net.Agents.Tools.ToolMessageExtensions 0%
OpenRouter.Net.Agents.Tools.ToolResult 100% 100%
OpenRouter.Net.Agents.Tools.Workspace 100% 91.6%
OpenRouter.Net.Imaging - 82.2%
Name Line Branch
OpenRouter.Net.Imaging 82.2% 59.3%
OpenRouter.Net.Imaging.ImageEncodeOptions 92.3% 75%
OpenRouter.Net.Imaging.ImageEncoder 80.3% 57.1%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 06/28/2026 - 18:20:08 | | Coverage date: | 06/28/2026 - 18:20:05 - 06/28/2026 - 18:20:06 | | Parser: | MultiReport (3x Cobertura) | | Assemblies: | 3 | | Classes: | 105 | | Files: | 105 | | **Line coverage:** | 75.4% (1358 of 1800) | | Covered lines: | 1358 | | Uncovered lines: | 442 | | Coverable lines: | 1800 | | Total lines: | 5264 | | **Branch coverage:** | 64.5% (448 of 694) | | Covered branches: | 448 | | Total branches: | 694 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>OpenRouter.Net - 68.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**OpenRouter.Net**|**68.8%**|**64.3%**| |OpenRouter.Net.Client.CachingOpenRouterClient|72.4%|81.2%| |OpenRouter.Net.Client.OpenRouterClient|85.4%|71.7%| |OpenRouter.Net.Client.OpenRouterClientOptions|0%|| |OpenRouter.Net.Client.OpenRouterRequestLoggingHandler|27.2%|7.1%| |OpenRouter.Net.Client.RetryOptions|80%|0%| |OpenRouter.Net.Extensions.ServiceCollectionExtensions|0%|0%| |OpenRouter.Net.Internal.ApiErrorEnvelope|100%|| |OpenRouter.Net.Internal.AssistantContent|92.8%|75%| |OpenRouter.Net.Internal.AssistantContentJsonConverter|75%|64.2%| |OpenRouter.Net.Internal.ContentPartListConverter|85.7%|66.6%| |OpenRouter.Net.Internal.ErrorParser|69.2%|62.5%| |OpenRouter.Net.Internal.JsonOptions|100%|| |OpenRouter.Net.Internal.ListEnvelope`1|100%|| |OpenRouter.Net.Internal.ObjectEnvelope`1|100%|| |OpenRouter.Net.Internal.RetryPolicy|90.9%|84.7%| |OpenRouter.Net.Internal.SseEventReader|91.6%|83.3%| |OpenRouter.Net.Internal.TolerantStringEnumConverter`1|90.1%|80%| |OpenRouter.Net.Internal.ToolChoiceJsonConverter|61.7%|46.1%| |OpenRouter.Net.Models.Common.ApiError|66.6%|| |OpenRouter.Net.Models.Common.CacheControl|100%|| |OpenRouter.Net.Models.Common.Result|100%|| |OpenRouter.Net.Models.Common.Result`1|60%|30%| |OpenRouter.Net.Models.Content.AudioPart|100%|| |OpenRouter.Net.Models.Content.ImagePart|100%|| |OpenRouter.Net.Models.Content.ImageUrl|100%|| |OpenRouter.Net.Models.Content.InputAudio|100%|| |OpenRouter.Net.Models.Content.TextPart|100%|| |OpenRouter.Net.Models.Messages.AssistantMessage|100%|70%| |OpenRouter.Net.Models.Messages.DeveloperMessage|0%|| |OpenRouter.Net.Models.Messages.SystemMessage|57.1%|| |OpenRouter.Net.Models.Messages.ToolMessage|54.5%|| |OpenRouter.Net.Models.Messages.UserMessage|100%|| |OpenRouter.Net.Models.Requests.ChatCompletionRequest|92.6%|58.3%| |OpenRouter.Net.Models.Requests.FunctionCall|100%|| |OpenRouter.Net.Models.Requests.FunctionDefinition|75%|| |OpenRouter.Net.Models.Requests.JsonSchemaSpec|0%|| |OpenRouter.Net.Models.Requests.ProviderPreferences|0%|| |OpenRouter.Net.Models.Requests.ReasoningEncryptedDetail|100%|| |OpenRouter.Net.Models.Requests.ReasoningOptions|100%|| |OpenRouter.Net.Models.Requests.ReasoningSummaryDetail|100%|| |OpenRouter.Net.Models.Requests.ReasoningTextDetail|100%|| |OpenRouter.Net.Models.Requests.ResponseFormat|0%|| |OpenRouter.Net.Models.Requests.ToolCall|75%|| |OpenRouter.Net.Models.Requests.ToolChoice|100%|| |OpenRouter.Net.Models.Requests.ToolDefinition|100%|| |OpenRouter.Net.Models.Requests.UsageOptions|0%|| |OpenRouter.Net.Models.Responses.ChatCompletionChunk|44.4%|0%| |OpenRouter.Net.Models.Responses.ChatCompletionResponse|80%|0%| |OpenRouter.Net.Models.Responses.Choice|93.7%|100%| |OpenRouter.Net.Models.Responses.CompletionTokensDetails|0%|| |OpenRouter.Net.Models.Responses.CreditsInfo|100%|| |OpenRouter.Net.Models.Responses.GenerationInfo|9%|| |OpenRouter.Net.Models.Responses.KeyInfo|0%|| |OpenRouter.Net.Models.Responses.ModelArchitecture|20%|| |OpenRouter.Net.Models.Responses.ModelEndpointsResponse|0%|| |OpenRouter.Net.Models.Responses.ModelInfo|20%|| |OpenRouter.Net.Models.Responses.ModelPricing|0%|| |OpenRouter.Net.Models.Responses.PromptTokensDetails|0%|| |OpenRouter.Net.Models.Responses.ProviderEndpoint|0%|| |OpenRouter.Net.Models.Responses.RateLimit|0%|| |OpenRouter.Net.Models.Responses.TopProvider|0%|| |OpenRouter.Net.Models.Responses.Usage|66.6%|| |OpenRouter.Net.OpenRouterException|83.3%|| |System.Text.RegularExpressions.Generated|83.3%|57.6%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FFC6B051A15CDCE5<br/>43F954A3762D43C2CD5DCD5CDF29C19D3789DC49A7DCA1C47__Base64DataUriPattern_0|82%|58.3%| </details> <details><summary>OpenRouter.Net.Agents - 81.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**OpenRouter.Net.Agents**|**81.7%**|**65.3%**| |OpenRouter.Net.Agents.Agent|92.1%|77.8%| |OpenRouter.Net.Agents.AgentCompletedEvent|100%|| |OpenRouter.Net.Agents.AgentEvent|100%|| |OpenRouter.Net.Agents.AgentOptions|100%|| |OpenRouter.Net.Agents.AgentResult|100%|50%| |OpenRouter.Net.Agents.AgentSnapshot|100%|| |OpenRouter.Net.Agents.AssistantTurnEvent|100%|| |OpenRouter.Net.Agents.Extensions.ServiceCollectionExtensions|0%|| |OpenRouter.Net.Agents.RoundDetail|87.5%|| |OpenRouter.Net.Agents.RoundEndEvent|100%|| |OpenRouter.Net.Agents.ToolCallCompletedEvent|100%|| |OpenRouter.Net.Agents.ToolCallSkippedEvent|100%|| |OpenRouter.Net.Agents.ToolCallStartedEvent|100%|| |OpenRouter.Net.Agents.ToolExecutionDetail|100%|| |OpenRouter.Net.Agents.Tools.FileSystem.CopyParams|100%|| |OpenRouter.Net.Agents.Tools.FileSystem.CopyTool|42.1%|21.4%| |OpenRouter.Net.Agents.Tools.FileSystem.DeleteParams|50%|| |OpenRouter.Net.Agents.Tools.FileSystem.DeleteTool|57.1%|50%| |OpenRouter.Net.Agents.Tools.FileSystem.DiffParams|100%|| |OpenRouter.Net.Agents.Tools.FileSystem.DiffTool|83.3%|77.1%| |OpenRouter.Net.Agents.Tools.FileSystem.ListDirectoryParams|100%|| |OpenRouter.Net.Agents.Tools.FileSystem.ListDirectoryTool|68%|61.1%| |OpenRouter.Net.Agents.Tools.FileSystem.MoveParams|100%|| |OpenRouter.Net.Agents.Tools.FileSystem.MoveTool|55%|33.3%| |OpenRouter.Net.Agents.Tools.FileSystem.ReadFileParams|100%|| |OpenRouter.Net.Agents.Tools.FileSystem.ReadFileTool|73.6%|60%| |OpenRouter.Net.Agents.Tools.FileSystem.SearchParams|100%|| |OpenRouter.Net.Agents.Tools.FileSystem.SearchTool|67.5%|70.8%| |OpenRouter.Net.Agents.Tools.FileSystem.WriteFileParams|100%|| |OpenRouter.Net.Agents.Tools.FileSystem.WriteFileTool|75%|62.5%| |OpenRouter.Net.Agents.Tools.ITool|100%|| |OpenRouter.Net.Agents.Tools.SubAgent.SubAgentParams|0%|| |OpenRouter.Net.Agents.Tools.SubAgent.SubAgentTool|0%|0%| |OpenRouter.Net.Agents.Tools.Tool`1|88.8%|50%| |OpenRouter.Net.Agents.Tools.ToolInvocationContext|100%|| |OpenRouter.Net.Agents.Tools.ToolMessageExtensions|0%|| |OpenRouter.Net.Agents.Tools.ToolResult|100%|100%| |OpenRouter.Net.Agents.Tools.Workspace|100%|91.6%| </details> <details><summary>OpenRouter.Net.Imaging - 82.2%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**OpenRouter.Net.Imaging**|**82.2%**|**59.3%**| |OpenRouter.Net.Imaging.ImageEncodeOptions|92.3%|75%| |OpenRouter.Net.Imaging.ImageEncoder|80.3%|57.1%| </details>
bjoern merged commit 3369bc052a into main 2026-06-28 20:21:01 +02:00
bjoern deleted branch chore/add-ci-pipeline 2026-06-28 20:21:01 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/OpenRouter.Net!1
No description provided.