Compare commits

...
Author SHA1 Message Date
d24244aae6
Honor the administration setup
All checks were successful
Dotnet build and test / build (push) Successful in 22m1s
2026-10-11 08:31:27 +01:00
2744619249
fix(postit): mount session banner in headless tests
All checks were successful
Dotnet build and test / build (push) Successful in 25m17s
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 07:28:17 +01:00
da35ac0a83
do not decide to attach dev tools from the compilation flag
Some checks failed
Dotnet build and test / build (push) Failing after 32m38s
2026-10-11 06:34:53 +01:00
875a020e0d
simpler, better, faster, stronger 2026-10-11 06:27:13 +01:00
eb347e6900
upload test resultes, in case of failure 2026-10-11 06:12:41 +01:00
c34941a4b9
no need for .env to test 2026-10-11 05:59:59 +01:00
afbefae111
make test>/dev/null 2026-10-11 05:43:03 +01:00
29a5b8be8d
run-on ubuntu-26.04, using the default container
All checks were successful
Dotnet build and test / build (push) Successful in 33m13s
2026-10-11 05:11:53 +01:00
d7e274c93a
run-on ubuntu-26.04 2026-10-11 05:08:24 +01:00
bc8ae74fc3
using pazof/yavsc-build-env:debian13-dotnet10-android36-jdk21-v4 2026-10-11 04:29:29 +01:00
b5fa7b9072
Github tests
All checks were successful
Dotnet build and test / build (pull_request) Successful in 26m40s
Dotnet build and test / build (push) Successful in 26m24s
2026-10-11 02:26:31 +01:00
7 changed files with 67 additions and 68 deletions

View file

@ -8,8 +8,7 @@ on:
jobs:
production-deploy:
runs-on: ubuntu-latest
runs-on: ubuntu-26.04
steps:
# 1. Récupération du code
- name: Check out the repo
@ -25,8 +24,24 @@ jobs:
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 10.0.x
- name: Workloads restore
run: dotnet workload restore
- name: Test
run: dotnet test --no-build --verbosity normal --filter="Category!=Platform-Android"
env:
TEST_FAILED: ""
run: make test || export TEST_FAILED=$?
- uses: actions/upload-artifact@v7
with:
name: my-artifact
path: test-reports/**/*
- name: Fail if tests failed
if: ${{ env.TEST_FAILED != '' && env.TEST_FAILED != '0' }}
run: exit $TEST_FAILED
# 4. Build et Push de l'image de production finale
- name: Build and push production image
uses: docker/build-push-action@v7

View file

@ -29,6 +29,11 @@
### Fixed
* [PostIt.Tests] Les tests du bandeau de session utilisent le bandeau
réellement monté dans la fenêtre headless, au lieu d'un contrôle détaché.
Les fenêtres sont fermées après chaque test et les quatre boutons
de navigation sont vérifiés.
* [PostIt] Les liens Markdown des pièces jointes utilisent désormais
le même filtre de nom que l'upload serveur, avant l'encodage URL.
Le libellé conserve le nom original, notamment ses accents.

View file

@ -1,36 +1,12 @@
# Utilisation de l'image globale pour le build et l'exécution
FROM pazof/yavsc-build-env:debian12-dotnet10-android36-v1 AS build-env
FROM pazof/yavsc-build-env:debian13-dotnet10-android36-jdk21-v4 AS build-env
WORKDIR /src
# 1. Copie de la solution
COPY *.sln ./
COPY Directory.*.props ./
# 2. Copie de l'intégralité des fichiers de projets (.csproj) pour la restauration
COPY src/Yavsc.Org/*.csproj ./src/Yavsc.Org/
COPY src/Yavsc.Abstract/*.csproj ./src/Yavsc.Abstract/
COPY src/Yavsc.Server/*.csproj ./src/Yavsc.Server/
COPY src/Yavsc.Api/*.csproj ./src/Yavsc.Api/
COPY src/Yavsc.Blogs/*.csproj ./src/Yavsc.Blogs/
COPY src/cli/*.csproj ./src/cli/
COPY src/Yavsc.Org.Tests/*.csproj ./src/Yavsc.Org.Tests/
# Projets associés à PostIt
COPY src/PostIt/PostIt/*.csproj ./src/PostIt/PostIt/
COPY src/PostIt/PostIt.Android/*.csproj ./src/PostIt/PostIt.Android/
COPY src/PostIt/PostIt.Browser/*.csproj ./src/PostIt/PostIt.Browser/
COPY src/PostIt/PostIt.Desktop/*.csproj ./src/PostIt/PostIt.Desktop/
# 4. Copie de l'intégralité du code source
COPY . .
RUN dotnet workload restore
# 4. Restauration des dépendances pour tous les projets
RUN dotnet restore
# 5. Copie de la totalité du code source (filtrée par votre .dockerignore)
COPY . .
# 6. Compilation du serveur Web principal
RUN dotnet build src/Yavsc.Org/Yavsc.Org.csproj -c Release --no-restore -clp:ErrorsOnly

View file

@ -5,8 +5,6 @@ DESTDIR=/tmp/yavsc
APP_PATH=srv/www/yavsc
APP_FULL_PATH=$(DESTDIR)/$(APP_PATH)
include .env
all:
dotnet build --nologo

View file

@ -1,6 +1,5 @@
using Avalonia.Controls;
using Avalonia.Headless.XUnit;
using Avalonia.Styling;
using Avalonia.VisualTree;
using PostIt.ViewModels;
using PostIt.Views;
@ -23,41 +22,36 @@ namespace PostIt.Tests;
/// <c>new Window().Show()</c> throws because the harness has not
/// been initialised for that thread.</para>
///
/// <para>The session banner's <c>DataContext</c> is not wired in
/// these tests: <c>App.OnFrameworkInitializationCompleted</c> is
/// not called in a unit test, so we set the DataContext on the
/// banner directly. The production code path is exercised
/// <para>These tests set the DataContext directly on the banner
/// mounted in the window. The production code path is exercised
/// end-to-end by the manual launch, not here.</para>
/// </summary>
public class SessionStatusBannerTests
public class SessionStatusBannerTests : IDisposable
{
[AvaloniaFact]
public void Banner_renders_three_buttons_in_the_visual_tree()
{
MainWindow window = new MainWindow();
window.Show();
private MainWindow? _window;
var buttons = window.GetVisualDescendants()
[AvaloniaFact]
public void Banner_renders_four_buttons_in_the_visual_tree()
{
SessionStatusBanner banner = CreateBanner();
var buttons = banner.GetVisualDescendants()
.OfType<Button>()
.ToList();
// Three buttons, named by their content text: Se
// déconnecter, Se connecter, Paramètres. If any one is
// missing, the user has no way to trigger the
// corresponding navigation event.
Assert.True(buttons.Count>=4);
Assert.Equal(4, buttons.Count);
Assert.Contains(buttons, b => b.Content as string == "Se déconnecter");
Assert.Contains(buttons, b => b.Content as string == "Se connecter");
Assert.Contains(buttons, b => b.Content as string == "Paramètres");
Assert.Contains(buttons, b => b.Content as string == "À propos");
}
[AvaloniaFact]
public void Banner_login_button_is_visible_when_logged_out()
{
MainWindow window = new MainWindow();
window.Show();
SessionStatusBanner banner = CreateBanner();
var login = window.GetVisualDescendants()
var login = banner.GetVisualDescendants()
.OfType<Button>()
.Single(b => b.Content as string == "Se connecter");
@ -71,10 +65,7 @@ public class SessionStatusBannerTests
{
SessionStatusBanner banner = CreateBanner();
Assert.False((banner.DataContext as SessionStatusViewModel)!
.IsLoggedIn); // VM default
Assert.False(Assert.IsType<SessionStatusViewModel>(banner.DataContext).IsLoggedIn);
var logout = banner.GetVisualDescendants()
.OfType<Button>()
.Single(b => b.Content as string == "Se déconnecter");
@ -91,22 +82,23 @@ public class SessionStatusBannerTests
.OfType<Button>()
.Single(b => b.Content as string == "Paramètres");
// Paramètres is the only button with no IsVisible
// binding — always shown. The user's only path to the
// settings page goes through this button.
// Paramètres has no IsVisible binding and is always shown.
Assert.True(settings.IsVisible);
}
private static SessionStatusBanner CreateBanner()
private SessionStatusBanner CreateBanner()
{
MainWindow window = new MainWindow();
window.Show();
var banner = new SessionStatusBanner();
var status = new SessionStatusViewModel();
banner.DataContext = status;
_window = new MainWindow();
_window.Show();
var banner = _window.GetVisualDescendants()
.OfType<SessionStatusBanner>()
.Single();
banner.DataContext = new SessionStatusViewModel();
return banner;
}
public void Dispose() => _window?.Close();
[AvaloniaFact]
public void Banner_session_label_reflects_DataContext()
{

View file

@ -44,10 +44,6 @@ public partial class App : Application, ILiveView, IVMPusherApp
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
#if DEBUG
this.AttachDeveloperTools();
#endif
}
public override void OnFrameworkInitializationCompleted()

View file

@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Yavsc.Abstract.Identity;
using Yavsc.Models;
using Yavsc.Server.Helpers;
@ -19,14 +20,20 @@ namespace Yavsc.Controllers
private readonly RoleManager<IdentityRole> _roleManager;
private readonly ApplicationDbContext _dbContext;
private readonly SiteSettings _siteSettings;
private readonly ILogger<AdministrationController> _logger;
public AdministrationController(UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
ApplicationDbContext context)
ApplicationDbContext context,
IOptions<SiteSettings> siteSettings,
ILoggerFactory loggerFactory)
{
_userManager = userManager;
_roleManager = roleManager;
this._dbContext = context;
this._siteSettings = siteSettings.Value;
this._logger = loggerFactory.CreateLogger<AdministrationController>();
}
@ -92,6 +99,16 @@ namespace Yavsc.Controllers
}
var user = await _userManager.FindByIdAsync(User.GetUserId());
var email = user.Email;
var isOk = email == _siteSettings.Owner?.EMail || _siteSettings.Admin?.EMail == email;
if (!isOk)
{
if (_siteSettings.Owner==null && _siteSettings.Admin==null)
_logger.LogError("No owner or admin is set in the site settings.");
return Forbid();
}
// check all user groups exist
if (!await EnsureRoleList())
{