Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "10.0.1",
"version": "10.0.11",
"commands": [
"dotnet-ef"
],
"rollForward": false
},
"microsoft.web.librarymanager.cli": {
"version": "3.0.71",
"version": "3.0.114",
"commands": [
"libman"
],
"rollForward": false
}
}
}
}
4 changes: 2 additions & 2 deletions .cursor/rules/k7-tests.mdc
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
description: K7 testing conventions (NUnit, FluentAssertions, NSubstitute)
description: K7 testing conventions (NUnit, AwesomeAssertions, NSubstitute)
globs: tests/**
alwaysApply: false
---

# Testing

Stack: NUnit, FluentAssertions, NSubstitute. AAA structure.
Stack: NUnit, AwesomeAssertions, NSubstitute. AAA structure.

Naming: `{ClassUnderTest}Tests`, `{Method}_Should{Expected}_When{Condition}`.

Expand Down
2 changes: 1 addition & 1 deletion .github/instructions/tests.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ applyTo: "tests/**"

Follow [docs/dev/developing.md - Testing](../../docs/dev/developing.md#testing) and [CONTRIBUTING.md](../../CONTRIBUTING.md) (tests with behavior changes).

Summary: NUnit, FluentAssertions, NSubstitute; AAA; naming `{ClassUnderTest}Tests` / `{Method}_Should{Expected}_When{Condition}`; bUnit in `Clients.ComponentTests`.
Summary: NUnit, AwesomeAssertions, NSubstitute; AAA; naming `{ClassUnderTest}Tests` / `{Method}_Should{Expected}_When{Condition}`; bUnit in `Clients.ComponentTests`.
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ No `Result<T>` wrapper pattern.

## Testing (`tests/**`)

NUnit, FluentAssertions, NSubstitute; bUnit for critical components. Naming: `{ClassUnderTest}Tests`, `{Method}_Should{Expected}_When{Condition}`. Full project list and CI filters: [docs/dev/developing.md](docs/dev/developing.md#testing).
NUnit, AwesomeAssertions, NSubstitute; bUnit for critical components. Naming: `{ClassUnderTest}Tests`, `{Method}_Should{Expected}_When{Condition}`. Full project list and CI filters: [docs/dev/developing.md](docs/dev/developing.md#testing).

## Git Conventions

Expand Down
94 changes: 42 additions & 52 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,18 @@
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="AngleSharp" Version="1.5.2" />
<PackageVersion Include="Ardalis.GuardClauses" Version="4.2.0" />
<PackageVersion Include="Aspire.Hosting.Docker" Version="13.5.3" />
<PackageVersion Include="Ardalis.GuardClauses" Version="4.6.0" />
<PackageVersion Include="Aspire.Hosting.PostgreSQL" Version="13.5.3" />
<!-- Transitive CVE pins (CentralPackageTransitivePinningEnabled). Not referenced directly. -->
<PackageVersion Include="Azure.Identity" Version="1.21.0" />
<PackageVersion Include="System.Drawing.Common" Version="10.0.11" />
<PackageVersion Include="Blazor-ApexCharts" Version="6.1.0" />
<PackageVersion Include="Blazored.LocalStorage" Version="4.5.0" />
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.7.0" />
<PackageVersion Include="bunit" Version="2.7.2" />
<PackageVersion Include="bunit" Version="2.9.0" />
<PackageVersion Include="CommunityToolkit.Maui.MediaElement" Version="9.0.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="FFMpegCore" Version="5.4.0" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="FluentValidation.AspNetCore" Version="11.3.1" />
<PackageVersion Include="AwesomeAssertions" Version="9.6.0" />
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
<PackageVersion Include="LibVLCSharp" Version="4.0.0-alpha-20260828-10280" />
<PackageVersion Include="LibVLCSharp.MAUI" Version="4.0.0-alpha-20260828-10280" />
Expand All @@ -39,15 +37,12 @@
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.11" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.11" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.11" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.1.6" />
<PackageVersion Include="Microsoft.OpenApi" Version="2.12.2" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.30" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.11" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.11" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.11" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.11" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.11" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.30" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.11" />
Expand All @@ -64,67 +59,62 @@
<PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.22.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.22.0" />
<PackageVersion Include="Microsoft.Maui.Controls" Version="10.0.100" />
<!-- MediaElement 9 pulls Compatibility 9.0.82; pin to 10 so Windows copy targets resolve. -->
<PackageVersion Include="Microsoft.Maui.Controls.Compatibility" Version="10.0.100" />
<PackageVersion Include="Microsoft.Maui.Essentials" Version="10.0.100" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="MockQueryable.NSubstitute" Version="10.0.5" />
<PackageVersion Include="Microsoft.TestPlatform.TestHost" Version="17.14.1" />
<PackageVersion Include="MockQueryable.NSubstitute" Version="10.0.8" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
<PackageVersion Include="Microsoft.Web.LibraryManager.Build" Version="3.0.114" />
<PackageVersion Include="NAudio" Version="2.2.1" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
<PackageVersion Include="NSubstitute" Version="5.3.0" />
<PackageVersion Include="NSubstitute.Analyzers.CSharp" Version="1.0.16" />
<PackageVersion Include="nunit" Version="4.0.1" />
<PackageVersion Include="NSubstitute.Analyzers.CSharp" Version="1.0.17" />
<PackageVersion Include="nunit" Version="4.6.1" />
<PackageVersion Include="NUnit.Analyzers" Version="3.10.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageVersion Include="OpenIddict.Abstractions" Version="7.2.0" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.15.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.1" />
<PackageVersion Include="OpenIddict" Version="7.2.0" />
<PackageVersion Include="OpenIddict.AspNetCore" Version="7.2.0" />
<PackageVersion Include="OpenIddict.Client" Version="7.2.0" />
<PackageVersion Include="OpenIddict.Client.SystemIntegration" Version="7.2.0" />
<PackageVersion Include="OpenIddict.Client.SystemNetHttp" Version="7.2.0" />
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="7.2.0" />
<PackageVersion Include="OpenIddict.Server" Version="7.2.0" />
<PackageVersion Include="OpenIddict.Server.AspNetCore" Version="7.2.0" />
<PackageVersion Include="Respawn" Version="6.2.0" />
<PackageVersion Include="MessagePack" Version="3.1.7" />
<PackageVersion Include="MessagePack.Annotations" Version="3.1.7" />
<PackageVersion Include="Scalar.AspNetCore.Microsoft" Version="2.11.10" />
<PackageVersion Include="SkiaSharp" Version="3.119.1" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.119.1" />
<PackageVersion Include="SourceGear.sqlite3" Version="3.50.4.5" />
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.3" />
<PackageVersion Include="SQLitePCLRaw.config.e_sqlite3" Version="3.0.3" />
<PackageVersion Include="SQLitePCLRaw.core" Version="3.0.3" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageVersion Include="OpenIddict.Abstractions" Version="7.6.1" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.18.0" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.18.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.18.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.18.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.18.0" />
<PackageVersion Include="OpenIddict" Version="7.6.1" />
<PackageVersion Include="OpenIddict.AspNetCore" Version="7.6.1" />
<PackageVersion Include="OpenIddict.Client" Version="7.6.1" />
<PackageVersion Include="OpenIddict.Client.SystemIntegration" Version="7.6.1" />
<PackageVersion Include="OpenIddict.Client.SystemNetHttp" Version="7.6.1" />
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="7.6.1" />
<PackageVersion Include="OpenIddict.Server" Version="7.6.1" />
<PackageVersion Include="OpenIddict.Server.AspNetCore" Version="7.6.1" />
<PackageVersion Include="Respawn" Version="6.2.1" />
<PackageVersion Include="Scalar.AspNetCore.Microsoft" Version="2.17.2" />
<PackageVersion Include="SkiaSharp" Version="3.119.4" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.119.4" />
<PackageVersion Include="SourceGear.sqlite3" Version="3.53.4" />
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.5" />
<PackageVersion Include="SQLitePCLRaw.config.e_sqlite3" Version="3.0.5" />
<PackageVersion Include="SQLitePCLRaw.core" Version="3.0.5" />
<PackageVersion Include="SQLitePCLRaw.lib.e_sqlite3" Version="3.50.3" />
<PackageVersion Include="SQLitePCLRaw.provider.e_sqlite3" Version="3.0.3" />
<PackageVersion Include="Serilog" Version="4.3.0" />
<PackageVersion Include="SQLitePCLRaw.provider.e_sqlite3" Version="3.0.5" />
<PackageVersion Include="Serilog" Version="4.4.0" />
<PackageVersion Include="SkiaSharp.Extended.UI.Maui" Version="3.0.0" />
<PackageVersion Include="SkiaSharp.Skottie" Version="3.119.1" />
<PackageVersion Include="SkiaSharp.Skottie" Version="3.119.4" />
<PackageVersion Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageVersion Include="SharpDX.Direct3D11" Version="4.2.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageVersion Include="Serilog.Sinks.OpenTelemetry" Version="4.2.0" />
<PackageVersion Include="Spectre.Console" Version="0.50.0" />
<PackageVersion Include="Svg.Skia" Version="3.0.0" />
<PackageVersion Include="Spectre.Console" Version="0.57.2" />
<PackageVersion Include="Svg.Skia" Version="3.7.0" />
<PackageVersion Include="System.CommandLine" Version="2.0.11" />
<PackageVersion Include="System.Drawing.Common" Version="10.0.11" />
<PackageVersion Include="System.Text.Json" Version="10.0.11" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
<PackageVersion Include="Testcontainers.MsSql" Version="4.14.0" />
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.14.0" />
<PackageVersion Include="SSH.NET" Version="2026.0.0" />
<PackageVersion Include="TMDbLib" Version="2.3.0" />
<PackageVersion Include="VideoLAN.LibVLC.Windows" Version="4.0.0-alpha-20260830" />
<PackageVersion Include="System.Security.Cryptography.Xml" Version="10.0.11" />
<PackageVersion Include="Xamarin.GooglePlayServices.Cast" Version="122.3.1" />
<PackageVersion Include="Xamarin.GooglePlayServices.Cast.Framework" Version="122.3.1" />
<PackageVersion Include="Xamarin.GooglePlayServices.Basement" Version="118.10.0.1" />
<PackageVersion Include="Xamarin.GooglePlayServices.Cast" Version="122.3.1.1" />
<PackageVersion Include="Xamarin.GooglePlayServices.Cast.Framework" Version="122.3.1.1" />
<PackageVersion Include="Xamarin.GooglePlayServices.Basement" Version="118.10.0.2" />
</ItemGroup>
</Project>
8 changes: 7 additions & 1 deletion docs/dev/developing.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ A normal `dotnet build` on `src/Server/Web` regenerates the document. Prefer sha

## Testing

Stack: **NUnit**, **FluentAssertions**, **NSubstitute**. Blazor component tests use **bUnit**. Naming: `{ClassUnderTest}Tests`, `{Method}_Should{Expected}_When{Condition}`.
Stack: **NUnit**, **AwesomeAssertions**, **NSubstitute**. Blazor component tests use **bUnit**. Naming: `{ClassUnderTest}Tests`, `{Method}_Should{Expected}_When{Condition}`.

New behavior should ship with tests in the matching project (unit, bUnit, functional, or integration). Prefer covering the happy path and important failure cases for Application handlers and critical UI.

Expand Down Expand Up @@ -159,6 +159,12 @@ Functional/integration tests need **Docker** (Testcontainers.PostgreSQL + Respaw

K7 uses **[Renovate](https://docs.renovatebot.com/)** (self-hosted via GitHub Actions), not Dependabot version updates. Config: [`renovate.json`](../../renovate.json). Workflow: [`.github/workflows/renovate.yml`](../../.github/workflows/renovate.yml) (weekly Monday + `workflow_dispatch`).

Renovate groups only packages that must bump together (OpenIddict, OpenTelemetry, SkiaSharp, LibVLC, SQLite natives, Google Play Services, Microsoft runtime minors). Everything else gets its own PR so one breaking bump cannot block the rest. Majors for `Microsoft.OpenApi` (incompatible with `Microsoft.AspNetCore.OpenApi` 10) are disabled.

`Directory.Packages.props` also pins some transitive packages (`Azure.Identity`, `System.Drawing.Common`, `SSH.NET`) to patched versions. CI fails `dotnet list package --vulnerable` if those pins are removed.

Commit messages use `chore(deps): bump <package> to vX.Y.Z`. Renovate always writes **one commit per PR** (it force-pushes that single commit when it rebases). Isolated per-package history therefore comes from ungrouped PRs, not from multiple commits inside a grouped PR.

The job installs the `maui-android` workload on the runner so NuGet restore works for MAUI, including `android-arm` (Fire Stick / 32-bit). It uses the workflow `GITHUB_TOKEN` (no secret required).

Repo **Settings -> Actions -> General** must allow Actions to create pull requests (write permissions). Without this, Renovate pushes branches but cannot open PRs.
Expand Down
56 changes: 50 additions & 6 deletions renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,63 @@
"prHourlyLimit": 0,
"prCreation": "immediate",
"rangeStrategy": "bump",
"commitMessageAction": "bump",
"commitMessageTopic": "{{depName}}",
"osvVulnerabilityAlerts": true,
"packageRules": [
{
"description": "Group all other NuGet packages (CPM)",
"description": "Group OpenIddict packages (must bump together)",
"matchManagers": ["nuget"],
"matchPackageNames": ["!/^Microsoft\\./", "!/^System\\./", "!/^Aspire\\./"],
"groupName": "nuget other"
"matchPackageNames": ["/^OpenIddict/"],
"groupName": "openiddict"
},
{
"description": "Group Microsoft / System / Aspire NuGet packages (CPM)",
"description": "Group OpenTelemetry packages",
"matchManagers": ["nuget"],
"matchPackageNames": ["/^Microsoft\\./", "/^System\\./", "/^Aspire\\./"],
"groupName": "nuget microsoft"
"matchPackageNames": ["/^OpenTelemetry/"],
"groupName": "opentelemetry"
},
{
"description": "Group SkiaSharp family",
"matchManagers": ["nuget"],
"matchPackageNames": ["/^SkiaSharp/", "Svg.Skia"],
"groupName": "skiasharp"
},
{
"description": "Group LibVLCSharp alpha stack",
"matchManagers": ["nuget"],
"matchPackageNames": ["/^LibVLCSharp/", "VideoLAN.LibVLC.Windows"],
"groupName": "libvlc"
},
{
"description": "Group SQLite native pins",
"matchManagers": ["nuget"],
"matchPackageNames": ["/^SQLitePCLRaw/", "SourceGear.sqlite3"],
"groupName": "sqlite-native"
},
{
"description": "Group Google Play Services / Cast",
"matchManagers": ["nuget"],
"matchPackageNames": ["/^Xamarin.GooglePlayServices/"],
"groupName": "google-play-services"
},
{
"description": "Group ASP.NET / EF / Extensions / Aspire minors and patches only",
"matchManagers": ["nuget"],
"matchPackageNames": [
"/^Microsoft\\.(AspNetCore|EntityFrameworkCore|Extensions)\\./",
"/^System\\.(Text\\.Json|IdentityModel)/",
"Microsoft.IdentityModel.Protocols.OpenIdConnect",
"/^Aspire\\./"
],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "microsoft runtime"
},
{
"description": "Microsoft.OpenApi 3.x is incompatible with Microsoft.AspNetCore.OpenApi 10",
"matchPackageNames": ["Microsoft.OpenApi"],
"matchUpdateTypes": ["major"],
"enabled": false
},
{
"description": "Group GitHub Actions updates",
Expand Down
1 change: 0 additions & 1 deletion src/Clients/MAUI/K7.Clients.MAUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Microsoft.Maui.Controls" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" />
<PackageReference Include="Microsoft.Maui.Essentials" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;

namespace K7.Server.Infrastructure.Database.Context.Identity
{
// Remove the "else if (EmailSender is IdentityNoOpEmailSender)" block from RegisterConfirmation.razor after updating with a real implementation.
public sealed class IdentityNoOpEmailSender : IEmailSender<ApplicationUser>
{
private readonly IEmailSender emailSender = new NoOpEmailSender();
namespace K7.Server.Infrastructure.Database.Context.Identity;

public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) =>
emailSender.SendEmailAsync(email, "Confirm your email", $"Please confirm your account by <a href='{confirmationLink}'>clicking here</a>.");
public sealed class IdentityNoOpEmailSender : IEmailSender<ApplicationUser>
{
public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) =>
Task.CompletedTask;

public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) =>
emailSender.SendEmailAsync(email, "Reset your password", $"Please reset your password by <a href='{resetLink}'>clicking here</a>.");
public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) =>
Task.CompletedTask;

public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) =>
emailSender.SendEmailAsync(email, "Reset your password", $"Please reset your password using the following code: {resetCode}");
}
public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) =>
Task.CompletedTask;
}
Loading
Loading