From ae5d09f392dd2b0eee0d62459fa4894dab42f9e3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 13:10:50 +0000 Subject: [PATCH 1/6] Add CONTRIBUTING.md with project contribution guidelines --- CONTRIBUTING.md | 87 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ae1ece0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,87 @@ +# Contributing + +## Getting started + +### Machine setup + +To begin you'll need Git, the .NET SDK, and a PostgreSQL instance set up on your machine. + +The `DockerUpdateGuard` repository uses Git as its source control system. If you haven't already installed it, you can download it [here](https://git-scm.com/downloads) or, if you prefer a GUI-based approach, try [GitHub Desktop](https://desktop.github.com/). + +Once Git is installed, you'll also need the .NET SDK matching the version targeted by the solution (currently `net10.0`). Instructions and downloads for your preferred OS can be found [here](https://dotnet.microsoft.com/download). + +The application connects to a PostgreSQL database at runtime and applies EF Core migrations automatically on startup. A local PostgreSQL instance (native or via Docker) is enough for development. + +Format checks rely on `reihitsu-format`, a .NET tool. Install it once with: + +```shell +dotnet tool install -g Reihitsu.Cli +``` + +> [!IMPORTANT] +> The above steps are a one-time setup for your machine and do not need to be repeated after the initial configuration. + +### Cloning the repository + +Now that your machine is set up, you can clone the `DockerUpdateGuard` repository. Open a terminal and run this command: + +```shell +git clone https://github.com/LarsLaskowski/DockerUpdateGuard.git +``` + +Cloning via SSH: + +```shell +git clone git@github.com:LarsLaskowski/DockerUpdateGuard.git +``` + +### Installing and building + +From within the folder where you've cloned the repo, restore, format, and build the solution with the following commands: + +```shell +dotnet restore DockerUpdateGuard.slnx +reihitsu-format ./ +dotnet build DockerUpdateGuard.slnx -c Release --no-restore +``` + +### Running tests + +```shell +dotnet test src\Tests\**\*.csproj -c Release --no-build --logger trx --collect:"XPlat Code Coverage" +``` + +To run a single test project or method, see the commands in `README.md` and `CLAUDE.md`. + +### Submitting a pull request + +If you'd like to contribute by fixing a bug, implementing a feature, or even correcting typos in the documentation, you'll need to submit a pull request. + +Before submitting a pull request, be sure to [rebase](https://www.atlassian.com/git/tutorials/merging-vs-rebasing) your branch onto the current `main`. Do not use `git merge` or the *merge* button provided by GitHub. + +For PR naming use the following convention: `[area] Description` (no period at the end). + +- For the area, use the affected project or feature (for example `Data`, `Telemetry`, `UI`, `Scanning`). +- For the description, do not reference an issue number in there. A clear, short summary of what the change entails is enough; there is room to elaborate in the description. + +When a PR is related to an issue, use the `Closes #issuenumber` syntax so the issue links to the PR automatically and closes when the PR is merged. + +Use before/after screenshots in the PR description when a change affects the UI. + +Follow the PR template in [`.github/PULL_REQUEST_TEMPLATE.md`](.github/PULL_REQUEST_TEMPLATE.md). + +## Code style + +Detailed C# code-style rules (naming, regions, formatting, XML docs, null handling) are documented in [`.github/instructions/csharp.instructions.md`](.github/instructions/csharp.instructions.md) and are binding for all contributions. Run `reihitsu-format ./` before opening a pull request. + +## Stability policy + +An essential consideration in every pull request is its impact on the system. Avoid introducing unnecessary breaking changes, performance or functional regressions, or negative impacts on usability. + +## Reporting security issues + +Do not report security vulnerabilities through public GitHub issues. See [`SECURITY.md`](SECURITY.md) for the private reporting process. + +## License + +By contributing to this project, you agree that your contributions will be licensed under the same [MIT License](LICENSE.md) that covers the project. From bd177673b87006926869ad5e864fd395c294e8d6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 13:14:50 +0000 Subject: [PATCH 2/6] Move CONTRIBUTING.md to docs and add unit test guidelines --- CONTRIBUTING.md => docs/CONTRIBUTING.md | 10 +- docs/UNIT_TESTS.md | 333 ++++++++++++++++++++++++ 2 files changed, 339 insertions(+), 4 deletions(-) rename CONTRIBUTING.md => docs/CONTRIBUTING.md (88%) create mode 100644 docs/UNIT_TESTS.md diff --git a/CONTRIBUTING.md b/docs/CONTRIBUTING.md similarity index 88% rename from CONTRIBUTING.md rename to docs/CONTRIBUTING.md index ae1ece0..f4d0290 100644 --- a/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -53,6 +53,8 @@ dotnet test src\Tests\**\*.csproj -c Release --no-build --logger trx --collect:" To run a single test project or method, see the commands in `README.md` and `CLAUDE.md`. +For detailed rules on how unit tests should be structured and named, see [`UNIT_TESTS.md`](UNIT_TESTS.md). + ### Submitting a pull request If you'd like to contribute by fixing a bug, implementing a feature, or even correcting typos in the documentation, you'll need to submit a pull request. @@ -68,11 +70,11 @@ When a PR is related to an issue, use the `Closes #issuenumber` syntax so the is Use before/after screenshots in the PR description when a change affects the UI. -Follow the PR template in [`.github/PULL_REQUEST_TEMPLATE.md`](.github/PULL_REQUEST_TEMPLATE.md). +Follow the PR template in [`.github/PULL_REQUEST_TEMPLATE.md`](../.github/PULL_REQUEST_TEMPLATE.md). ## Code style -Detailed C# code-style rules (naming, regions, formatting, XML docs, null handling) are documented in [`.github/instructions/csharp.instructions.md`](.github/instructions/csharp.instructions.md) and are binding for all contributions. Run `reihitsu-format ./` before opening a pull request. +Detailed C# code-style rules (naming, regions, formatting, XML docs, null handling) are documented in [`.github/instructions/csharp.instructions.md`](../.github/instructions/csharp.instructions.md) and are binding for all contributions. Run `reihitsu-format ./` before opening a pull request. ## Stability policy @@ -80,8 +82,8 @@ An essential consideration in every pull request is its impact on the system. Av ## Reporting security issues -Do not report security vulnerabilities through public GitHub issues. See [`SECURITY.md`](SECURITY.md) for the private reporting process. +Do not report security vulnerabilities through public GitHub issues. See [`SECURITY.md`](../SECURITY.md) for the private reporting process. ## License -By contributing to this project, you agree that your contributions will be licensed under the same [MIT License](LICENSE.md) that covers the project. +By contributing to this project, you agree that your contributions will be licensed under the same [MIT License](../LICENSE.md) that covers the project. diff --git a/docs/UNIT_TESTS.md b/docs/UNIT_TESTS.md new file mode 100644 index 0000000..3ff2891 --- /dev/null +++ b/docs/UNIT_TESTS.md @@ -0,0 +1,333 @@ +# How to create Unit Tests for DockerUpdateGuard + +## Overview + +This document describes how unit tests are written in this repository. It is +binding for both human contributors and AI agents: new tests must follow the +conventions below, and existing tests are the reference implementation — when +in doubt, look at a neighboring test file in the same project before inventing +a new pattern. + +## Unit tests vs. other test types + +1. **Unit tests** + + A unit test exercises an individual component or method in isolation. Unit + tests should only test code within the developer's control; they do not + exercise infrastructure concerns such as real databases, file systems, or + network resources. + +2. **Integration tests** + + An integration test exercises two or more components working together and + often does include infrastructure concerns. The `DockerUpdateGuard.Data.Tests` + project sits closer to this category: it runs real EF Core migrations and + queries against a SQLite database rather than mocking the data layer. + +3. **Load tests** + + DockerUpdateGuard does not currently have load tests. + +## Why unit test? + +- **Fast feedback.** Unit tests run in milliseconds and don't require manual + steps through the UI. +- **Protection against regression.** The full suite can be rerun after every + change to confirm existing behavior still holds. +- **Executable documentation.** A well-named test tells the reader what a + method does for a given input without needing to read its implementation. +- **Less coupled code.** Code that is hard to unit test is usually a sign of + tight coupling; writing the test first tends to produce better-decoupled + designs. + +## Test stack + +| Concern | Tooling | +| --- | --- | +| Test framework | MSTest (`[TestClass]`, `[TestMethod]`, `[DataRow]`) | +| Mocking | NSubstitute (`Substitute.For()`, `Arg.Any()`) | +| Blazor component rendering | bUnit, wired up for MudBlazor via `Helper.BlazorTestContextFactory.Create()` | +| EF Core (host/app layer, `DockerUpdateGuard.Tests`) | `Microsoft.EntityFrameworkCore.InMemory` | +| EF Core (data layer, `DockerUpdateGuard.Data.Tests`) | SQLite in-memory via `Data.SqliteTestDatabase` | +| Code coverage | `coverlet.collector`, collected via `dotnet test --collect:"XPlat Code Coverage"` | + +Do not introduce xUnit, NUnit, FluentAssertions, or Moq — the project standardizes on +MSTest, NSubstitute, and MSTest's own `Assert`/`CollectionAssert` APIs. + +## Where tests live + +- Tests live under `src\Tests`, never in a top-level `tests` folder. +- `src\Tests\DockerUpdateGuard.Tests` tests the main host/application layer + (services, background jobs, Blazor components, EF Core InMemory). +- `src\Tests\DockerUpdateGuard.Data.Tests` tests the data layer (repositories, + query services, migrations) against SQLite. +- Shared test doubles and fakes live in a project-local `Helper` folder (for + example `DockerUpdateGuard.Tests\Helper`), reusable EF Core test fixtures in + a `Data` folder (for example `Data.SqliteTestDatabase`). +- One test file per type under test. Name the file `{TypeUnderTest}Tests.cs`. + When a single type has distinct concerns worth separating (for example + rendering vs. persisted state of a Blazor page), split into + `{TypeUnderTest}RenderTests.cs` and `{TypeUnderTest}PersistentStateTests.cs` + rather than growing one file indefinitely. + +## Naming your tests + +Test method names are a single PascalCase identifier with no underscores, +built from three parts, concatenated directly: + +- The name of the **type or method** being tested. +- The **scenario** under which it's being tested. +- The **expected behavior** when the scenario is invoked. + +Async test methods keep the `Async` suffix as the final word. + +**Why?** The name alone must explain the test's intent when it shows up in a +failing test list, without opening the file. + +**Examples from this codebase:** + +```csharp +public void ImageReferenceParserParseWrappedMicrosoftRegistryReferenceNormalizesRegistry() +public void UpdateDetectionServiceDigestChangeReturnsCurrentTagUpdate() +public void MyImagesGetSectionTitleWithUserNameIncludesUserName() +public async Task VulnerabilityEnrichmentServiceRefreshAsyncProviderExceptionMarksRunFailedAsync() +``` + +Test class names follow `{TypeUnderTest}Tests` (or `{TypeUnderTest}RenderTests` +/ `{TypeUnderTest}PersistentStateTests` when split, per `CLAUDE.md`). + +## Arranging your tests + +Follow Arrange, Act, Assert without labeling the sections with comments — a +blank line before the act and before the assert block is enough to separate +them, consistent with the blank-line rules in +`.github/instructions/csharp.instructions.md`. + +```csharp +[TestMethod] +public void ImageReferenceParserParseWrappedMicrosoftRegistryReferenceNormalizesRegistry() +{ + var parser = new ImageReferenceParser(); + + var imageReference = parser.Parse("docker.io/mcr.microsoft.com/mssql/server:2019-CU32-GDR7-ubuntu-20.04"); + + Assert.AreEqual("mcr.microsoft.com", + imageReference.Registry, + "Wrapped Microsoft registry references must be normalized back to mcr.microsoft.com"); +} +``` + +## Always include an assertion message + +Every `Assert.*` call must include a message explaining what the assertion +guarantees — not what it checks mechanically, but why it matters. This is a +binding project rule (see `CLAUDE.md`), not a suggestion: + +```csharp +Assert.AreEqual(UpdateEvaluationStatus.UpdateAvailable, + evaluation.Status, + "A changed digest on the current tag must be treated as an available update"); +``` + +Prefer MSTest's own `Assert` and `CollectionAssert` members +(`Assert.AreEqual`, `Assert.HasCount`, `Assert.AreSequenceEqual`, +`Assert.Contains`, `Assert.IsTrue`, ...) directly instead of FluentAssertions. + +## Write minimally passing tests + +Use the simplest input that exercises the behavior under test. Minimal tests +stay resilient to unrelated changes elsewhere in the type and keep the focus +on behavior rather than implementation details. + +## Avoid logic in tests + +Do not add `if`, `for`, `while`, or `switch` statements inside a test body. +When multiple inputs must be checked against the same behavior, use MSTest's +`[DataRow]` on a single parameterized `[TestMethod]` instead of writing +conditional logic: + +```csharp +[TestMethod] +[DataRow("Completed", Color.Success)] +[DataRow("Failed", Color.Error)] +[DataRow("Running", Color.Info)] +public void MyImagesGetScanStatusColorKnownStatusReturnsExpectedColor(string status, Color expectedColor) +{ + var color = (Color)_getScanStatusColorMethod.Invoke(null, [status])!; + + Assert.AreEqual(expectedColor, + color, + $"Scan status '{status}' must map to Color.{expectedColor}"); +} +``` + +## Prefer helper methods over constructor setup + +MSTest constructs a fresh test class instance per test, so shared state is +already isolated. Even so, do not build shared fixtures in the constructor. +Use a private (or private static) helper method instead, following the +`#region Static methods` / `#region Methods` ordering from +`.github/instructions/csharp.instructions.md`: + +```csharp +#region Static methods + +/// +/// Create an update detection service with the default scanning options +/// +/// Update detection service +private static UpdateDetectionService CreateService() +{ + return CreateService(new ScanningOptions()); +} + +#endregion // Static methods +``` + +**Why?** All setup relevant to a test stays visible from the call site, and +there is no risk of over-setting-up state that later tests then depend on. + +## Avoid multiple acts + +Include a single logical action per test. When a scenario needs multiple +related outcomes checked, that's still one act followed by multiple +assertions — not multiple acts. Add a separate `[TestMethod]`, or a +`[DataRow]`-parameterized test, for each distinct scenario instead of +branching within one test. + +## Mocking with NSubstitute + +Use `Substitute.For()` for collaborators and `Arg.Any()` / +`Arg.Is()` to configure return values: + +```csharp +var vulnerabilityProvider = Substitute.For(); + +vulnerabilityProvider.GetVulnerabilitiesAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromException>>(new InvalidOperationException("provider boom"))); +``` + +Only mock the collaborators you don't own or that touch infrastructure +(HTTP clients, external providers). Prefer exercising real, in-process +collaborators (parsers, calculators, EF Core against InMemory/SQLite) over +mocking them, so the test also verifies the wiring between them. + +## Testing EF Core-backed code + +- In `DockerUpdateGuard.Tests`, use `Microsoft.EntityFrameworkCore.InMemory` + for tests that need a `DbContext` but are not verifying SQL-specific or + migration behavior. +- In `DockerUpdateGuard.Data.Tests`, use the `Data.SqliteTestDatabase` helper, + which opens a `:memory:` SQLite connection and calls + `Database.EnsureCreated()`, so repository and query-service tests run + against real EF Core query translation: + +```csharp +using (var database = new SqliteTestDatabase()) +{ + var dbContext = database.CreateDbContext(); + + await using (dbContext.ConfigureAwait(false)) + { + var repository = new ImageCatalogRepository(dbContext); + + // Act + Assert against the repository + } +} +``` + +- Migration tests (`Update{N}MigrationTests.cs`) follow the migration naming + rules from `CLAUDE.md` (`InitialCreate`, `Update1`, `Update2`, ...). + +## Testing Blazor components + +Blazor page and component tests use bUnit, configured for MudBlazor through +`Helper.BlazorTestContextFactory.Create()`. Register only the services the +component under test needs, render it, and assert against the rendered +markup: + +```csharp +var testContext = BlazorTestContextFactory.Create(); + +await using (testContext) +{ + var viewService = Substitute.For(); + + viewService.GetDashboardAsync(Arg.Any()) + .Returns(Task.FromResult(new DashboardViewData { /* ... */ })); + + testContext.Services.AddSingleton(viewService); + + var component = testContext.Render(); + var markup = component.Markup; + + Assert.Contains("Observed Images", markup, "The Observed Images metric tile must render"); +} +``` + +For non-public members that a component intentionally does not expose +publicly (for example a static markup-formatting helper), reflection via +`MethodInfo` is an accepted pattern in this codebase — see +`MyImagesTests.cs` for the reference implementation. + +## XML documentation on tests + +Per `.github/instructions/csharp.instructions.md`, XML documentation is +required on all members, including test classes and test methods. Document +what the test verifies, not what MSTest attribute it carries: + +```csharp +/// +/// Verify digest changes on the current tag produce an update +/// +[TestMethod] +public void UpdateDetectionServiceDigestChangeReturnsCurrentTagUpdate() +{ + // ... +} +``` + +## Code coverage + +Code coverage is collected with `coverlet.collector`, already referenced by +both test projects — no separate installation is needed to collect coverage +during `dotnet test`. + +Run the full suite with coverage collection, as documented in `README.md` and +`CLAUDE.md`: + +```shell +dotnet test src\Tests\**\*.csproj -c Release --no-build --logger trx --collect:"XPlat Code Coverage" +``` + +This produces a `coverage.cobertura.xml` file per test project under its +`TestResults` folder. To turn those into a browsable HTML report locally, +install [ReportGenerator](https://reportgenerator.io) once: + +```shell +dotnet tool install --global dotnet-reportgenerator-globaltool +``` + +and merge the reports: + +```shell +reportgenerator "-reports:src\Tests\**\TestResults\**\coverage.cobertura.xml" "-targetdir:coverage-report" -reporttypes:Html +``` + +## Checklist for new tests + +- [ ] Test class named `{TypeUnderTest}Tests` (or `...RenderTests` / + `...PersistentStateTests` when split), in the matching test project. +- [ ] Test method named `{TypeUnderTest}{Scenario}{ExpectedResult}` (PascalCase, + no underscores, `Async` suffix for async tests). +- [ ] `[TestClass]` / `[TestMethod]` (MSTest), `[DataRow]` instead of in-test + branching for multiple inputs. +- [ ] Arrange / Act / Assert, separated by blank lines, one act per test. +- [ ] Every `Assert.*` call includes an explanatory message. +- [ ] Collaborators mocked with NSubstitute only where they cross an + infrastructure boundary; real objects and real EF Core otherwise. +- [ ] Shared setup factored into a private/static helper method, not a + constructor. +- [ ] `#region` layout and XML docs follow + `.github/instructions/csharp.instructions.md`. +- [ ] `reihitsu-format ./` run before committing. From d9ef2f24cdf6bfd68e4b605f339da35a3d8e7d89 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 13:19:19 +0000 Subject: [PATCH 3/6] Update PR template with description, test plan and checklist --- .github/PULL_REQUEST_TEMPLATE.md | 66 +++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 39e7429..2fc2826 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,19 +1,65 @@ -## Summary + +Search open/closed issues before submitting. Someone may have pushed the same thing before! -## Why +Provide a summary of your changes in the title field above. +--> - +# Pull Request -## Linked issues +## 📖 Description - + -## Review notes +### 🎫 Issues - + -## Follow-up work +## 👩‍💻 Reviewer Notes - + + +## 📑 Test Plan + + + +## ✅ Checklist + +### General + + + +- [ ] I have added tests for my changes. +- [ ] I have tested my changes. +- [ ] I have updated the project documentation to reflect my changes. +- [ ] I have read the [CONTRIBUTING](../docs/CONTRIBUTING.md) documentation and followed the project's [code style guidelines](../.github/instructions/csharp.instructions.md). + +### UI-specific + + + + +- [ ] I have added a new Blazor page/component. +- [ ] I have added [Unit Tests](../docs/UNIT_TESTS.md) for the new page/component. +- [ ] I have modified an existing Blazor page/component. +- [ ] I have updated the [Unit Tests](../docs/UNIT_TESTS.md) for the modified page/component. + +## ⏭ Next Steps + + From 63be8f200c43253db18363c43f2c7965e2145393 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 13:28:16 +0000 Subject: [PATCH 4/6] Remove duplicate GenerateDocumentationFile property --- src/DockerUpdateGuard/DockerUpdateGuard.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DockerUpdateGuard/DockerUpdateGuard.csproj b/src/DockerUpdateGuard/DockerUpdateGuard.csproj index 362e3ea..a08e64a 100644 --- a/src/DockerUpdateGuard/DockerUpdateGuard.csproj +++ b/src/DockerUpdateGuard/DockerUpdateGuard.csproj @@ -8,7 +8,6 @@ True Linux . - True true False $(NoWarn);SA1204;SA1600;SA1611 From 7451a49f39984e85f08801496e00a8be701fd30b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 13:28:20 +0000 Subject: [PATCH 5/6] Enforce formatting check in CI and release workflows --- .github/workflows/ci.yml | 6 ++++++ .github/workflows/release.yml | 6 ++++++ src/DockerUpdateGuard/Images/UpdateDetectionService.cs | 4 ++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd6ee47..1b785d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,12 @@ jobs: - name: Restore run: dotnet restore DockerUpdateGuard.slnx + - name: Install Reihitsu.Cli + run: dotnet tool install -g Reihitsu.Cli + + - name: Check formatting + run: reihitsu-format --check . + - name: Begin SonarQube analysis if: ${{ env.SONAR_TOKEN != '' }} run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7892685..51eebe7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,6 +33,12 @@ jobs: - name: Restore run: dotnet restore DockerUpdateGuard.slnx + - name: Install Reihitsu.Cli + run: dotnet tool install -g Reihitsu.Cli + + - name: Check formatting + run: reihitsu-format --check . + - name: Build run: dotnet build DockerUpdateGuard.slnx -c Release --no-restore diff --git a/src/DockerUpdateGuard/Images/UpdateDetectionService.cs b/src/DockerUpdateGuard/Images/UpdateDetectionService.cs index e7b3109..05305fe 100644 --- a/src/DockerUpdateGuard/Images/UpdateDetectionService.cs +++ b/src/DockerUpdateGuard/Images/UpdateDetectionService.cs @@ -418,8 +418,8 @@ private static bool IsCandidatePublishedAfterBaseline(DateTimeOffset? candidateP /// Major version of the requested version line /// Tags of the requested major version line that carry a publication timestamp private static List<(DockerHubTagData Tag, Version Version)> GetMajorLineTags(IReadOnlyList orderedTags, - string currentTag, - int major) + string currentTag, + int major) { var currentIsPreRelease = VersionTagResolutionHelper.IsPreReleaseVersionTag(currentTag); From 5864a92dbb887f276982a7f9afcf5d9739b913d1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 13:28:24 +0000 Subject: [PATCH 6/6] Update Copilot instructions to reflect current project state --- .github/copilot-instructions.md | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index c720e96..ff12f31 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -41,17 +41,17 @@ There is no dedicated lint command at the moment beyond formatting. Static analy ## High-level architecture -The repository is currently a solution skeleton with project wiring in place, but almost no implementation files yet. The architecture is defined by the solution layout, project references, and package choices: +The repository contains a complete implementation: Docker/DockerHub/Portainer clients, an EF Core data layer with migrations, a Blazor UI, and a telemetry layer. The architecture is defined by the solution layout, project references, and package choices: | Path | Role | | --- | --- | -| `src\DockerUpdateGuard` | Main ASP.NET Core host (`Microsoft.NET.Sdk.Web`); references the data and telemetry projects | -| `src\DockerUpdateGuard.Data` | Data-access layer; prepared for EF Core with PostgreSQL via `Npgsql.EntityFrameworkCore.PostgreSQL` | -| `src\DockerUpdateGuard.Telemetry` | Shared observability layer; prepared for OpenTelemetry hosting, OTLP export, ASP.NET Core, HTTP, and runtime instrumentation | +| `src\DockerUpdateGuard` | Main ASP.NET Core host (`Microsoft.NET.Sdk.Web`); composition root; references the data and telemetry projects | +| `src\DockerUpdateGuard.Data` | Data-access layer; EF Core with PostgreSQL via `Npgsql.EntityFrameworkCore.PostgreSQL` | +| `src\DockerUpdateGuard.Telemetry` | Shared observability layer; OpenTelemetry hosting, OTLP export, ASP.NET Core, HTTP, and runtime instrumentation | | `src\Tests\DockerUpdateGuard.Tests` | Tests for the main host/application layer; references the web project and uses EF Core InMemory plus NSubstitute | | `src\Tests\DockerUpdateGuard.Data.Tests` | Tests for the data layer; references the data project and uses EF Core SQLite | -The main host project is the composition root. It is expected to keep web startup and dependency wiring, while persistence stays in `.Data` and observability stays in `.Telemetry`. +Web startup and dependency wiring stay in the main host project; persistence stays in `.Data`; observability stays in `.Telemetry`. ## Key conventions @@ -63,19 +63,13 @@ The main host project is the composition root. It is expected to keep web startu - Tests are under `src\Tests`, not a top-level `tests` folder. Keep new test projects there. - The current test stack is MSTest with `coverlet.collector`. - Detailed C# formatting and style rules live in `.github\instructions\csharp.instructions.md`. Follow that file for naming, region layout, XML docs, and null-handling preferences. - -## Current state note - -- Target the latest stable .NET version used by the solution template. The reference project currently uses `net10.0`. -- Enable nullable reference types, implicit usings, and XML documentation files in every main project. -- Link shared files like `SharedAssemblyInfo.cs` into each project when the solution is created. -- Use `Reihitsu.Analyzer` as a build-time analyzer. -- Use MSTest with `coverlet.collector` for tests. - Prefer MSTest's `Assert` and `CollectionAssert` APIs directly instead of FluentAssertions. - Name test classes `{Feature}Tests` and test methods `{Class}{Scenario}{ExpectedResult}`. - Always include assertion messages in tests. -If the project adds EF Core migrations, follow the same migration pattern as SeriesOverwatch: +## EF Core migrations + +The project already has EF Core migrations; follow the same pattern for new ones: - first migration: `InitialCreate` - later migrations: `Update1`, `Update2`, `Update3`, ...