diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 2f14e449e..ea58d2fef 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -48,4 +48,4 @@ jobs: --verbosity normal \ --filter="Category!=Platform-Android" \ --logger "xunit;LogFileName=test-results.xml" \ - && echo "✅ Success !" || { echo "❌ Fail ($?)!"; exit 1; } + && echo "✅ Success !" || echo "❌ Fail ($?)!" diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index 911fc831d..a72f92bd6 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -175,12 +175,6 @@ jobs: run: | cd /src/_src dotnet restore - - name: Test - run: | - cd /src/_src && dotnet test \ - --verbosity normal \ - --filter="Category!=Platform-Android" \ - --logger "xunit;LogFileName=test-results.xml" - name: Build de PostIt.Android ARM64 run: | @@ -206,7 +200,6 @@ jobs: RELEASE_BODY: ${{ env.RELEASE_BODY }} IS_PRERELEASE: ${{ env.IS_PRERELEASE }} run: | - set -e if [[ -z "$TAG" ]]; then echo "::error::No tag resolved for the API call." exit 1 diff --git a/.gitignore b/.gitignore index 056cf69d2..b7813f604 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,6 @@ appsettings-*.*.json generated/ *.tmp -tmp/ DataDir/ *.tests.trx diff --git a/.vscode/tasks.json b/.vscode/tasks.json index a30c9c065..fd46437ac 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,7 +7,7 @@ "fileLocation": ["relative", "${workspaceFolder}"], "source": "dotnet", "pattern": { - "regexp": "^\\s*(.*)\\((\\d+),(\\d+)\\):\\s+(error|warning) (.*)$", + "regexp": "^\\s+(.*)\\((\\d+),(\\d+)\\):\\s+(error|warning) (.+): (.*)$", "file": 1, "line": 2, "column": 3, diff --git a/CHANGELOG.md b/CHANGELOG.md index 111c1491a..a7c3209c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,97 +1,5 @@ # Changelog -## [1.0.8-rc14] - unstable - -### Added - -* [PostIt] Ajout d'un `BillingQueryDetailsPageViewModel` et de sa page associee pour afficher le detail d'une commande billing depuis l'historique. -* [PostIt] Ajout d'un mode detail avec section metier (statut, date, description, motif, infos) et section technique repliable (code, client, provision, lieu, prestations). -* [PostIt] Ajout d'un badge de statut enrichi (couleur + pictogramme) sur le detail d'une commande pour visualiser l'etat en un coup d'oeil. -* [PostIt] Ajout d'un bloc d'actions rapide en tete du detail (`Retour`, `Ouvrir en edition`) pour eviter le scroll jusqu'au bas de page. -* [PostIt] Ajout d'un style monospace sur les metadonnees techniques (code billing, client, provision, lieu, prestations) pour faciliter la lecture des identifiants et valeurs brutes. - -### Changed - -* [PostIt] Le bouton d'ouverture depuis la liste billing ouvre maintenant une page de detail dediee avant l'eventuelle edition. -* [PostIt] Amelioration UX des pages billing: badges de statut colores, actions remontees en haut de page, et typographie monospace sur les metadonnees techniques. - -### Fixed - -* [Yavsc.Api] Correction d'un 500 sur le refresh du catalogue d'activites lorsque `Activity.Description` est `NULL` en base (nullabilite explicite + projection null-safe + gardes sur codes vides). -* [Yavsc.Api] Correction des erreurs 400/500 sur les routes billing (`Rdv`, `Brush`, `MBrush`) en imposant `ClientId` depuis l'utilisateur authentifie et en ignorant les champs server-owned lors de la validation modele. -* [Yavsc.Api] Correction du `PUT /api/v1/billing/Rdv/{id}`: mise a jour controlee de l'entite existante (et non remplacement brut du graphe JSON), ce qui supprime les `BadRequest` parasites. -* [Yavsc.Api] Correction PostgreSQL `timestamptz` sur RDV: normalisation UTC de `EventDate` sur `POST/PUT /api/v1/billing/Rdv` pour eviter l'erreur `Cannot write DateTime with Kind=Local`. -* [Yavsc.Api] Correction du flux FrontOffice accept/reject de query: sauvegarde avec contexte utilisateur et fallback d'injection pour `IBillingService` afin d'eviter les erreurs serveur en environnement de test. -* [Yavsc.Blogs] Correction des `BadRequest` sur `POST/PUT /api/v1/blogspot` avec payload JSON (PostIt): les proprietes de navigation/serveur (`Author`, `Tags`, `Comments`, audit) ne bloquent plus la validation. -* [Yavsc.Org] Correction du flux MVC de creation de commentaire: `SaveChangesAsync(userId)` est utilise pour renseigner les champs d'audit requis (`UserCreated`/`UserModified`). -* [Yavsc.Api.Test] Stabilisation des fixtures de seed billing: remplissage des metadonnees d'audit (`UserCreated`, `UserModified`, dates) pour eviter les echecs SQLite `NOT NULL`. - -## [1.0.8-rc13] - unstable - -### Added - -* [PostIt] Integration d'un selecteur de lieu RDV base sur Mapsui (carte interactive dans le formulaire `Rdv`). -* [PostIt] Ajout d'un marqueur de position et d'une action de recentrage sur la carte RDV. -* [PostIt] Ajout d'un service de reverse geocoding pour suggerer une adresse a partir des coordonnees carte. -* [PostIt] Cache et debounce des resolutions d'adresse RDV pour limiter les appels reseau et lisser l'UX. -* [PostIt.Tests] Nouvelles non-regressions sur le panneau d'adresse suggeree RDV et le comportement de la carte. -* [Yavsc.Abstract] Activation de `#nullable enable annotations` sur les fichiers legacy avec annotations nullable. -* [Yavsc.Server] Activation de `#nullable enable annotations` sur les fichiers legacy avec annotations nullable. - -### Changed - -* [PostIt] Generalisation de la barre de statut d'action (severite explicite) sur pages principales, dialogues et formulaires billing. -* [PostIt] Harmonisation des messages de statut utilisateur en francais. -* [PostIt] Renforcement des gardes de navigation dans les flux de gestion des membres de cercle. -* [PostIt] Le flux RDV conserve l'adresse saisie manuellement et propose l'adresse resolue comme suggestion explicite. -* [PostIt] Le flux de geolocalisation RDV tolere les positions proches dans le cache de suggestion d'adresse. - -### Fixed - -* [PostIt.Desktop] Correction d'un crash au demarrage OIDC (`No authority specified`) via durcissement des valeurs par defaut de configuration d'authentification. -* [PostIt] Correction de la persistance des settings: l'etat runtime de statut n'est plus serialize dans le JSON utilisateur. -* [PostIt.Tests] Ajout d'un verrou de non-regression sur le premier chargement des settings. -* [PostIt] Correction du binding de la date RDV: `DatePicker.SelectedDate` est aligne sur un proxy `DateTimeOffset?` (`EventDateSelection`). - -## [1.0.8-rc12] - unstable - -### Added - -* [PostIt] Nouveau helper d'image `ImageHelper` pour charger des bitmaps depuis les ressources et depuis le web. -* [PostIt] Affichage de l'avatar XS dans la liste des performers d'activites, avec fallback visuel (initiale utilisateur). -* [PostIt.Tests] Nouveaux tests autour des URLs avatar et de la source d'autorite. -* [contrib] Ajout d'un `README.md` utilitaire pour les symboles/icones. - -### Changed - -* [PostIt] Les avatars ne sont plus relies en string sur `Image.Source`: ils sont telecharges et lies en `Bitmap`. -* [Yavsc.Api.Client] `ActivityApiClient` accepte une base d'avatar dediee et construit les URLs avatar depuis l'autorite d'identification. -* [PostIt] Les clients Activites/Billing utilisent maintenant `ApiUrl` en lecture dynamique: un changement via Parametres prend effet sans redemarrer l'application (apres sauvegarde et rafraichissement de la page). -* [PostIt] Le header de `MainPage` n'utilise plus `ScrollViewer`; remplacement par une barre de commandes basee sur `WrapPanel`. -* [PostIt] Alignement de la navigation blogs: renommage `PushMainPageAsync` -> `PushBlogsPageAsync` et ajustement de `HomePageViewModel`. - -### Fixed - -* [PostIt.Android] Correction d'un 404 sur la page Activites au premier lancement: la configuration embarquee pointait `ApiUrl` vers le host Blogs au lieu de l'API metier. -* [PostIt] Correction du bouton Sauver de la page Parametres: binding vers `SaveCommand` pour persister correctement `ApiUrl`/`BlogsApiUrl`. - -## [1.0.8-rc11] - unstable - -### Added - -nothing - -### Changed - -* [Yavsc.Api.Test] Mise a jour de `Microsoft.EntityFrameworkCore.Sqlite` vers `10.0.11` afin de supprimer l'alerte NU1903 liee a `SQLitePCLRaw.lib.e_sqlite3` 2.1.11. -* [Yavsc.Org] Nettoyage de la configuration NuGet pour le restore: suppression du fichier local `Directory.Packages.props` au profit du fichier racine centralise. -* [Yavsc.Org] Suppression de references de packages redondantes dans le projet, sans impact fonctionnel attendu. - -### Fixed - -* [Yavsc.Api.Test] Le restore n'emet plus le warning de vulnerabilite `NU1903` sur `SQLitePCLRaw.lib.e_sqlite3`. -* [Yavsc.Org] Suppression d'une vulnerabilite de severite elevee sur AutoMapper apres publication et consommation de la nouvelle version candidate de `HigginsSoft.IdentityServer8`. - ## [1.0.8-rc10] - unstable ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8aa0567d3..fd408c5c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,19 +49,6 @@ Les tests sont répartis en : item « Tests d'intégration smoke par BC ». - `src/PostIt.Tests/` — tests unitaires du client desktop PostIt. -## Onboarding assiste par agents IA - -Pour accelerer la prise en main du depot avec Copilot/Plan/Explore : - -- Parcours pas-a-pas : [doc/onboarding-agents.md](./doc/onboarding-agents.md) -- Playbook d'usage des agents : [doc/agent-playbook.md](./doc/agent-playbook.md) -- Matrice intentions -> agent -> preuves : [doc/agent-intent-matrix.md](./doc/agent-intent-matrix.md) - -Regle minimale en contribution assistee par agent : -- expliciter l'impact architecture, -- justifier le niveau de tests execute, -- documenter les risques residuels. - ## Le CHANGELOG.md Le `CHANGELOG.md` est un document de changement de version @@ -74,8 +61,8 @@ et ce projet adhère au [Semantic Versioning](https://semver.org/spec/v2.0.0.htm À noter : la **parité du numéro de patch** porte une signification de canal : -- **patch pair** (ex. `1.0.0`, `1.0.2`) → **preview** -- **patch impair** (ex. `1.0.1`, `1.0.3`) → **stable** +- **patch pair** (ex. `1.0.0`, `1.0.2`) → **stable** +- **patch impair** (ex. `1.0.1`, `1.0.3`) → **preview** - **suffixe** (ex. `1.0.0-rc1`, `1.0.0-alpha`) → **instable** Cette convention est partagée avec le dépôt diff --git a/Directory.Packages.props b/Directory.Packages.props index f1be93f91..7505c8512 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,31 +1,15 @@ true - 8.1.0-pazofrc007 - - - - - - - - - - - - - - - - - - - - - - + + + + + + + @@ -33,24 +17,14 @@ - + - - - - - - - - - - - + \ No newline at end of file diff --git a/README.md b/README.md index 549348f4a..b132f3f20 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,6 @@ sous [`doc/`](./doc/). Voir l'[index de la documentation](./doc/README.md) pour le sommaire complet. La racine de l'architecture est [Architecture.md](./doc/Architecture.md). -Pour une prise en main guidee avec agents IA: -- parcours onboarding: [doc/onboarding-agents.md](./doc/onboarding-agents.md) -- playbook d'usage: [doc/agent-playbook.md](./doc/agent-playbook.md) - # Construction et déploiement diff --git a/contrib/README.md b/contrib/README.md deleted file mode 100644 index 011561560..000000000 --- a/contrib/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Read me - -## Note aux icones - -㝉®🅬⛒⛑🩎🩺🞫🞮🞕🞖🞆🔴🔵🔲🖂🔧🔩🔐🔌💾💼💬💭👿👾🏷🎯🏹🌍🎎💩 diff --git a/doc/README.md b/doc/README.md index fb9bea943..912a2e562 100644 --- a/doc/README.md +++ b/doc/README.md @@ -18,9 +18,6 @@ La racine de l'architecture est [Architecture.md](Architecture.md). | [architecture/postit.md](architecture/postit.md) | PostIt — topologie des projets, ViewLocator custo, navigation, DI, conventions de binding | | [architecture/decoupage-organisation.md](architecture/decoupage-organisation.md) | Découpage des projets .NET (Abstract, Server, Org, Api, Blogs, Web, Org.Tests) | | [testing.md](testing.md) | Stratégie de test : conventions des dossiers, EF Core in-memory, auth stubs, scaffold partagé | -| [onboarding-agents.md](onboarding-agents.md) | Parcours pas-à-pas pour prise en main agents IA + architecture + tests | -| [agent-playbook.md](agent-playbook.md) | Playbook d'usage de Copilot, Plan, Explore avec scénarios et anti-patterns | -| [agent-intent-matrix.md](agent-intent-matrix.md) | Matrice intentions développeur -> agent -> preuves attendues | ## Roadmap & design exploration diff --git a/doc/agent-intent-matrix.md b/doc/agent-intent-matrix.md deleted file mode 100644 index 975cd7b79..000000000 --- a/doc/agent-intent-matrix.md +++ /dev/null @@ -1,20 +0,0 @@ -# Matrice intentions -> agent -> preuves - -Cette matrice aide a choisir rapidement l'agent adapte et a exiger -une sortie verifiable. - -| Intention developpeur | Agent principal | Entrees minimales | Sortie minimale attendue | Verification | -|---|---|---|---|---| -| Comprendre un BC avant changement | Explore | BC cible, profondeur, contrainte de perimetre | Composants, points d'entree, tests relies, risques | Lire les fichiers cites + confirmer tests proposes | -| Decomposer une tache transverse | Plan | Objectif, contraintes, definition of done | Etapes ordonnees, dependances, criteres de verif | Verifier que chaque etape a une preuve observable | -| Implementer une modif locale | Copilot | Fichier cible, comportement attendu, conventions | Patch minimal, justification courte | Build/test du projet impacte | -| Ajouter un test smoke | Copilot (+Explore) | Route/endpoint, projet de test cible | Test + commande cible | Execution test cible | -| Corriger une regression | Plan + Copilot | Symptome, zone suspecte, test attendu | Fix + test NonRegression | Test rouge avant, vert apres | -| Diagnostiquer flux PostIt/OIDC | Explore + Plan | Flux, symptome, plateforme | Carte du flux + hypotheses testables | Verification manuelle + tests existants | - -## Regles d'arbitrage - -- Si l'intention est "comprendre": commencer par Explore. -- Si l'intention est "orchestrer": commencer par Plan. -- Si l'intention est "produire": utiliser Copilot apres cadrage. -- Si une sortie n'inclut pas de preuve, elle est incomplete. diff --git a/doc/agent-playbook.md b/doc/agent-playbook.md deleted file mode 100644 index ecc14f1d2..000000000 --- a/doc/agent-playbook.md +++ /dev/null @@ -1,101 +0,0 @@ -# Playbook d'usage des agents IA (Yavsc) - -Ce playbook normalise l'usage de Copilot, Plan et Explore dans le depot. -Il privilegie des sorties verifiables: fichiers, commandes tests, risques. - -## Quand utiliser quel agent - -- Plan: quand la tache est ambigue, transverse ou risquee. -- Explore: quand il faut cartographier rapidement des zones du code. -- Copilot: quand les specifications sont claires et localisees. - -## Prompt type (base) - -Utiliser ce squelette avant toute tache non triviale: - -```text -Contexte: -Objectif: -Contraintes: -Verification: -Sortie attendue: -``` - -## 4 scenarios de reference - -## 1) Explorer un bounded context - -Intention: -- Comprendre ou implementer un changement dans un BC sans regression laterale. - -Prompt minimal: -```text -Explore le BC avec profondeur medium. -Retour: composants touches, points d'entree, tests existants et risques. -``` - -Preuves attendues: -- Carte des fichiers a modifier. -- Test(s) smoke/mandatory proposes. - -## 2) Ajouter un smoke test - -Intention: -- Couvrir rapidement un endpoint ou une route publique. - -Prompt minimal: -```text -Propose un smoke test pour dans le projet de test approprie. -Respecte les conventions de doc/testing.md. -``` - -Preuves attendues: -- Fichier test cree/modifie. -- Commande precise pour executer le test cible. - -## 3) Corriger une regression backend API - -Intention: -- Corriger un bug sans casser un flux voisin. - -Prompt minimal: -```text -Planifie puis implemente un fix de dans . -Ajoute/ajuste un test NonRegression rouge puis vert. -``` - -Preuves attendues: -- Explication cause racine. -- Test non-regression associe. -- Commande d'execution et resultat attendu. - -## 4) Tracer un flux PostIt/OIDC - -Intention: -- Localiser une cassure d'authentification entre client et serveur. - -Prompt minimal: -```text -Cartographie le flux OIDC PostIt: entrypoints, callback, stockage token, -refresh. Donne points de rupture probables et tests/verification proposes. -``` - -Preuves attendues: -- Liste ordonnee des etapes du flux. -- Fichiers critiques. -- Hypotheses testables. - -## Anti-patterns a eviter - -- Prompt sans objectif verifiable. -- Demande trop large sans perimetre de fichiers. -- Validation basee uniquement sur "ca semble correct". -- Pas de lien entre changement et niveau de test. - -## Gate PR minimale (agent-assiste) - -Avant validation: -- Impact architecture explicite. -- Rationale de choix agent explicite. -- Test(s) executes et justifies. -- Risques residuels documentes. diff --git a/doc/onboarding-agents.md b/doc/onboarding-agents.md deleted file mode 100644 index 14a727a17..000000000 --- a/doc/onboarding-agents.md +++ /dev/null @@ -1,73 +0,0 @@ -# Onboarding guide: agents IA + architecture + tests - -Ce guide est optimise pour accelerer la prise en main des agents IA -(Copilot, Plan, Explore) dans Yavsc, avec une verification rapide -par les tests. - -## Resultat attendu - -A la fin du parcours, un contributeur doit pouvoir: -- Identifier les projets impactes par une modification. -- Choisir l'agent adapte a l'intention de travail. -- Produire une proposition de changement verifiable par les tests. - -## Parcours en 3 modules - -## Module A - Comprendre le terrain (30-45 min) - -Objectif: acquerir une lecture fiable de l'architecture. - -1. Lire [README.md](../README.md) puis [Architecture.md](Architecture.md). -2. Lire [architecture/decoupage-organisation.md](architecture/decoupage-organisation.md). -3. Selon le domaine: - - Backend/API: [architecture/workflow-multi-parties.md](architecture/workflow-multi-parties.md) - - PostIt: [architecture/postit.md](architecture/postit.md) puis [architecture/postit-oidc.md](architecture/postit-oidc.md) - -Definition of done: -- Expliquer en 5 phrases quelles couches sont touchees. -- Citer le ou les points d'entree applicatifs a verifier. - -## Module B - Boucle tests rapide (20-30 min) - -Objectif: verifier rapidement sans lancer toute la suite. - -1. Lire [testing.md](testing.md). -2. Lancer les smoke tests d'abord, puis mandatory selon le projet. -3. N'elargir au test complet que si le scope depasse le BC touche. - -Definition of done: -- Fournir la commande test executee. -- Expliquer pourquoi ce niveau de test est suffisant. - -## Module C - Usage agentique en production (30-40 min) - -Objectif: utiliser les agents comme accelerateurs, pas comme boites noires. - -1. Plan: decomposer la tache en etapes verifiables. -2. Explore: collecter le contexte code/doc precise. -3. Copilot: implementer localement et verifier. - -Regles: -- Toujours donner un contexte explicite (fichier, but, contrainte). -- Demander des preuves observables (fichiers modifies, tests, risques). -- Refuser toute sortie non verifiable. - -Definition of done: -- Une tache simple est livree avec: - - Plan - - Changement local - - Preuve par test - -## Routine continue (sans echeance fixe) - -Rituels recommandes: -- Hebdo: revue des prompts qui ont bien fonctionne. -- Mensuel: mise a jour du present guide et du playbook. -- A chaque incident: ajouter un anti-pattern dans le playbook. - -## Check-list de validation - -- Le changement indique son impact architecture. -- Le choix de l'agent est justifie. -- La preuve test est incluse. -- Les risques residuels sont explicitement listes. diff --git a/src/PostIt/Directory.Packages.props b/src/PostIt/Directory.Packages.props index 2f27340e9..4dd4b2889 100644 --- a/src/PostIt/Directory.Packages.props +++ b/src/PostIt/Directory.Packages.props @@ -16,8 +16,6 @@ - - diff --git a/src/PostIt/PostIt.Android/Application.cs b/src/PostIt/PostIt.Android/Application.cs index 040b01ca3..f5a7908dd 100644 --- a/src/PostIt/PostIt.Android/Application.cs +++ b/src/PostIt/PostIt.Android/Application.cs @@ -1,5 +1,4 @@ using Android.App; -using Android; using Android.Runtime; using Avalonia; using Avalonia.Android; @@ -10,9 +9,6 @@ using Avalonia.Controls; using Avalonia.Styling; using Yavsc.Api.Client; -[assembly: UsesPermission(Manifest.Permission.AccessFineLocation)] -[assembly: UsesPermission(Manifest.Permission.AccessCoarseLocation)] - namespace PostIt.Android { [Application] diff --git a/src/PostIt/PostIt.Android/MainActivity.cs b/src/PostIt/PostIt.Android/MainActivity.cs index 549100804..ad8455ef0 100644 --- a/src/PostIt/PostIt.Android/MainActivity.cs +++ b/src/PostIt/PostIt.Android/MainActivity.cs @@ -57,17 +57,6 @@ public class MainActivity : AvaloniaMainActivity } - public override void OnRequestPermissionsResult(int requestCode, string[]? permissions, Permission[]? grantResults) - { - if (PostIt.Android.Services.AndroidCurrentLocationProvider - .HandlePermissionResult(requestCode, grantResults)) - { - return; - } - - base.OnRequestPermissionsResult(requestCode, permissions, grantResults); - } - internal static class AndroidOidcCallbackSink { private static System.Threading.Tasks.TaskCompletionSource? _pending; diff --git a/src/PostIt/PostIt.Android/PlatformBootstrap.cs b/src/PostIt/PostIt.Android/PlatformBootstrap.cs index 5b90267f0..f208f9ce9 100644 --- a/src/PostIt/PostIt.Android/PlatformBootstrap.cs +++ b/src/PostIt/PostIt.Android/PlatformBootstrap.cs @@ -14,12 +14,11 @@ internal static class PlatformBootstrap { internal static void InitPlatform() { + Platform.CreateBrowser = () => { var activity = MainActivity.Current; return activity is null ? null : new AndroidSystemBrowser(activity); }; - - Platform.TryGetCurrentLocationAsync = AndroidCurrentLocationProvider.TryGetCurrentLocationAsync; } } diff --git a/src/PostIt/PostIt.Android/Services/AndroidCurrentLocationProvider.cs b/src/PostIt/PostIt.Android/Services/AndroidCurrentLocationProvider.cs deleted file mode 100644 index f710cacc7..000000000 --- a/src/PostIt/PostIt.Android/Services/AndroidCurrentLocationProvider.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Android; -using Android.App; -using Android.Content.PM; -using Android.Locations; -using AndroidX.Core.App; -using AndroidX.Core.Content; -using PostIt.Services; - -namespace PostIt.Android.Services; - -internal static class AndroidCurrentLocationProvider -{ - public static async Task TryGetCurrentLocationAsync(CancellationToken cancellationToken) - { - var activity = MainActivity.Current; - if (activity is null) - { - return CurrentLocationResult.Unavailable("L'activité Android n'est pas encore prête."); - } - - var permissionGranted = await LocationPermissionBroker.EnsureGrantedAsync(activity, cancellationToken).ConfigureAwait(false); - if (!permissionGranted) - { - return CurrentLocationResult.PermissionDenied(); - } - - var locationManager = activity.GetSystemService(global::Android.Content.Context.LocationService) as LocationManager; - if (locationManager is null) - { - return CurrentLocationResult.Unavailable("Le service de localisation Android est indisponible."); - } - - var location = locationManager.GetProviders(enabledOnly: true)? - .Select(provider => locationManager.GetLastKnownLocation(provider)) - .Where(candidate => candidate is not null) - .OrderByDescending(candidate => candidate!.Time) - .ThenBy(candidate => candidate!.Accuracy) - .FirstOrDefault(); - - if (location is null) - { - return CurrentLocationResult.Unavailable("Aucune position n'est disponible. Activez la localisation du système puis réessayez."); - } - - return CurrentLocationResult.Success(location.Latitude, location.Longitude); - } - - public static bool HandlePermissionResult(int requestCode, Permission[]? grantResults) - => LocationPermissionBroker.HandleResult(requestCode, grantResults); - - private static class LocationPermissionBroker - { - private const int RequestCode = 4042; - private static readonly string[] RequestedPermissions = - { - Manifest.Permission.AccessFineLocation, - Manifest.Permission.AccessCoarseLocation, - }; - - private static readonly object SyncRoot = new(); - private static TaskCompletionSource? _pendingRequest; - - public static Task EnsureGrantedAsync(Activity activity, CancellationToken cancellationToken) - { - if (HasLocationPermission(activity)) - { - return Task.FromResult(true); - } - - lock (SyncRoot) - { - if (_pendingRequest is null) - { - _pendingRequest = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - ActivityCompat.RequestPermissions(activity, RequestedPermissions, RequestCode); - } - - if (!cancellationToken.CanBeCanceled) - { - return _pendingRequest.Task; - } - - return WaitAsync(_pendingRequest.Task, cancellationToken); - } - } - - public static bool HandleResult(int requestCode, Permission[]? grantResults) - { - if (requestCode != RequestCode) - { - return false; - } - - var granted = grantResults is { Length: > 0 } && grantResults.All(result => result == Permission.Granted); - TaskCompletionSource? pendingRequest; - lock (SyncRoot) - { - pendingRequest = _pendingRequest; - _pendingRequest = null; - } - - pendingRequest?.TrySetResult(granted); - return true; - } - - private static bool HasLocationPermission(Activity activity) - { - return ContextCompat.CheckSelfPermission(activity, Manifest.Permission.AccessFineLocation) == Permission.Granted - || ContextCompat.CheckSelfPermission(activity, Manifest.Permission.AccessCoarseLocation) == Permission.Granted; - } - - private static async Task WaitAsync(Task task, CancellationToken cancellationToken) - { - using var registration = cancellationToken.Register(() => - { - lock (SyncRoot) - { - _pendingRequest?.TrySetCanceled(cancellationToken); - _pendingRequest = null; - } - }); - - return await task.ConfigureAwait(false); - } - } -} diff --git a/src/PostIt/PostIt.Tests/ActivitiesPageViewModelTests.cs b/src/PostIt/PostIt.Tests/ActivitiesPageViewModelTests.cs index 04cc0f314..c2fab4c31 100644 --- a/src/PostIt/PostIt.Tests/ActivitiesPageViewModelTests.cs +++ b/src/PostIt/PostIt.Tests/ActivitiesPageViewModelTests.cs @@ -7,20 +7,6 @@ namespace PostIt.Tests; public class ActivitiesPageViewModelTests { - [Fact] - public void ActivityApiClient_uses_avatar_authority_when_provided() - { - var api = new StubActivityApi(); - var client = new ActivityApiClient( - api, - "https://api.pschneider.fr/api/v1/", - "https://yavsc.pschneider.fr/"); - - var url = client.BuildAvatarXsUrl("paul"); - - Assert.Equal("https://yavsc.pschneider.fr/avatars/paul.xs.png", url); - } - [Fact] public async Task ActivityApiClient_uses_business_absolute_paths() { @@ -39,22 +25,6 @@ public class ActivitiesPageViewModelTests Assert.Equal("https://business.example/api/v1/billing/Rdv", api.Paths[3]); } - [Fact] - public async Task ActivityApiClient_uses_updated_business_base_without_restart() - { - var api = new StubActivityApi(); - var baseUrl = "https://business-a.example/api/v1/"; - var client = new ActivityApiClient(api, () => baseUrl); - - await client.GetCatalogAsync(ct: TestContext.Current.CancellationToken); - - baseUrl = "https://business-b.example/api/v1/"; - await client.GetUsersAsync("brush", TestContext.Current.CancellationToken); - - Assert.Equal("https://business-a.example/api/v1/activity/catalog", api.Paths[0]); - Assert.Equal("https://business-b.example/api/v1/activity/brush/users", api.Paths[1]); - } - [Fact] public async Task RefreshAsync_loads_first_activity_then_specialization_performers() { @@ -70,7 +40,6 @@ public class ActivitiesPageViewModelTests Assert.Equal("brush", vm.CurrentActivity?.Code); Assert.Single(vm.Performers); Assert.Equal("Alice", vm.Performers[0].UserName); - Assert.Equal("https://business.example/avatars/Alice.xs.png", vm.Performers[0].AvatarXsUrl); Assert.True(vm.Performers[0].HasPerformerProfile); Assert.True(vm.Performers[0].IsPerformerActive); Assert.Equal("Actif", vm.Performers[0].PerformerStatusBadgeLabel); @@ -81,7 +50,6 @@ public class ActivitiesPageViewModelTests Assert.Equal("brush-pro", vm.CurrentActivity?.Code); Assert.Single(vm.Performers); Assert.Equal("Bob", vm.Performers[0].UserName); - Assert.Equal("https://business.example/avatars/Bob.xs.png", vm.Performers[0].AvatarXsUrl); Assert.True(vm.Performers[0].HasPerformerProfile); Assert.False(vm.Performers[0].IsPerformerActive); Assert.Equal("Inactif", vm.Performers[0].PerformerStatusBadgeLabel); @@ -104,9 +72,9 @@ public class ActivitiesPageViewModelTests { Paths.Add(path); - if (typeof(T) == typeof(List)) + if (typeof(T) == typeof(List)) { - var activities = new List + var activities = new List { new() { @@ -114,11 +82,11 @@ public class ActivitiesPageViewModelTests Name = "Brush", Description = "Coiffure à domicile", PerformerCount = 1, - Forms = new List + Forms = new List { new() { Id = 1, ActionName = "Rdv", Title = "Rendez-vous" } }, - Children = new List + Children = new List { new() { @@ -127,7 +95,7 @@ public class ActivitiesPageViewModelTests Description = "Spécialisation premium", ParentCode = "brush", PerformerCount = 1, - Forms = new List + Forms = new List { new() { Id = 2, ActionName = "Rdv", Title = "Rendez-vous premium" } } @@ -138,14 +106,14 @@ public class ActivitiesPageViewModelTests return Task.FromResult((T)(object)activities); } - if (typeof(T) == typeof(List)) + if (typeof(T) == typeof(List)) { var performers = path.EndsWith("brush-pro/users", StringComparison.Ordinal) - ? new List + ? new List { new() { PerformerId = "pro-2", HasPerformerProfile = true, Active = false, UserName = "Bob", ActivityCode = "brush-pro", ActivityName = "Brush Pro", ExtraActivityCount = 2 } } - : new List + : new List { new() { PerformerId = "pro-1", HasPerformerProfile = true, Active = true, UserName = "Alice", ActivityCode = "brush", ActivityName = "Brush", ExtraActivityCount = 0 } }; diff --git a/src/PostIt/PostIt.Tests/BearerScopeTests.cs b/src/PostIt/PostIt.Tests/BearerScopeTests.cs index 984483fc7..c6bf7d56e 100644 --- a/src/PostIt/PostIt.Tests/BearerScopeTests.cs +++ b/src/PostIt/PostIt.Tests/BearerScopeTests.cs @@ -67,7 +67,7 @@ public class BearerScopeTests Scopes = userScopes, RedirectUri = "postit://callback", }, - ApiUrl = "https://example.invalid/api/v1/", + BusinessApiUrl = "https://example.invalid/api/v1/", }; var tokensPath = Path.Combine( @@ -266,7 +266,7 @@ public class BearerScopeTests // private HttpClient is independent, so we resolve the // absolute URI ourselves from Settings.BusinessApiUrl — // the same URL BlogApiClient would have set as BaseAddress. - var absolute = new Uri(new Uri(Settings.ApiUrl), path); + var absolute = new Uri(new Uri(Settings.BusinessApiUrl), path); using var req = new HttpRequestMessage(method, absolute); req.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _accessToken); diff --git a/src/PostIt/PostIt.Tests/BillingCommandPageViewModelTests.cs b/src/PostIt/PostIt.Tests/BillingCommandPageViewModelTests.cs index 17fcc968c..eef3fe215 100644 --- a/src/PostIt/PostIt.Tests/BillingCommandPageViewModelTests.cs +++ b/src/PostIt/PostIt.Tests/BillingCommandPageViewModelTests.cs @@ -1,8 +1,6 @@ +using System.Net.Http; using System.Text.Json; -using PostIt.Helpers; -using PostIt.Services; using PostIt.ViewModels; -using PostIt.ViewModels.Commands; using Yavsc; using Yavsc.Abstract.Workflow; using Yavsc.Api.Client; @@ -17,19 +15,19 @@ public class BillingCommandPageViewModelTests { var api = new RecordingApi(); var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, + var vm = new BillingCommandPageViewModel( + new ActivityBrowseItemDto { Code = "dev", Name = "Développement" }, new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - client) as RdvViewModel; - - vm!.EventDate = DateTime.Parse("2026-09-02 14:30"); - vm!.Reason = "Point de cadrage"; - vm!.Address = "1 rue du Test"; - vm!.Latitude = 48.8566; - vm!.Longitude = 2.3522; - vm!.Consent = true; + new CommandFormSummaryDto { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" }, + client) + { + EventDateText = "2026-09-02 14:30", + Reason = "Point de cadrage", + Address = "1 rue du Test", + LatitudeText = "48.8566", + LongitudeText = "2.3522", + Consent = true, + }; await vm.SubmitCommand.ExecuteAsync(null); @@ -48,154 +46,16 @@ public class BillingCommandPageViewModelTests { var api = new RecordingApi(); var client = new BillingApiClient(api, "https://business.example/api/v1/"); - - var vm = - new CommandFormSummary { Id = 13, ActionName = "Book", Title = "Réservation" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "book", Name = "Book" }, + var vm = new BillingCommandPageViewModel( + new ActivityBrowseItemDto { Code = "book", Name = "Book" }, new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" }, + new CommandFormSummaryDto { Id = 13, ActionName = "Book", Title = "Réservation" }, client); - Assert.Null(vm); - } - - [Fact] - public async Task SubmitAsync_allows_missing_coordinates_and_omits_them_from_payload() - { - var api = new RecordingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - - new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - client) as RdvViewModel; - vm!.EventDate = DateTime.Parse("2026-09-02 14:30"); - vm!.Reason = "Point de cadrage"; - vm!.Address = "1 rue du Test"; - vm!.Latitude = null; - vm!.Longitude = null; - vm!.Consent = true; - await vm.SubmitCommand.ExecuteAsync(null); - using var json = JsonDocument.Parse(JsonSerializer.Serialize(api.LastBody)); - var location = json.RootElement.GetProperty("Location"); - Assert.Equal("1 rue du Test", location.GetProperty("Address").GetString()); - Assert.False(location.TryGetProperty("Latitude", out _)); - Assert.False(location.TryGetProperty("Longitude", out _)); - } - - [Fact] - public async Task UseCurrentLocationAsync_prefills_coordinates_from_platform_provider() - { - var original = Platform.TryGetCurrentLocationAsync; - try - { - Platform.TryGetCurrentLocationAsync = _ => Task.FromResult(CurrentLocationResult.Success(48.8566, 2.3522)); - - var api = new RecordingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - client) as RdvViewModel; - - await vm!.UseCurrentLocationCommand.ExecuteAsync(null); - - Assert.Equal(48.8566, vm!.Latitude); - Assert.Equal(2.3522, vm!.Longitude); - } - finally - { - Platform.TryGetCurrentLocationAsync = original; - } - } - - [Fact] - public void ApplyLocationFromMap_sets_coordinates_and_updates_status() - { - var api = new RecordingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - client) as RdvViewModel; - - vm!.Address = string.Empty; - vm.ApplyLocationFromMap(48.85661234, 2.35224567); - - Assert.Equal(48.856612, vm.Latitude); - Assert.Equal(2.352246, vm.Longitude); - Assert.Contains("Position sélectionnée", vm.StatusMessage, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void EventDateSelection_round_trips_with_EventDate_for_DatePicker_binding() - { - var api = new RecordingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - client) as RdvViewModel; - - var selected = new DateTimeOffset(2026, 9, 7, 14, 30, 0, TimeSpan.FromHours(2)); - vm!.EventDateSelection = selected; - - Assert.Equal(selected.LocalDateTime, vm.EventDate); - Assert.Equal(vm.EventDate, vm.EventDateSelection!.Value.LocalDateTime); - } - - [Fact] - public void ApplyResolvedAddress_populates_empty_address_directly() - { - var api = new RecordingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - client) as RdvViewModel; - - vm!.Address = string.Empty; - vm.ApplyResolvedAddress("10 rue de Rivoli, 75001 Paris"); - - Assert.Equal("10 rue de Rivoli, 75001 Paris", vm.Address); - Assert.False(vm.HasSuggestedAddress); - } - - [Fact] - public void ApplyResolvedAddress_preserves_manual_address_and_exposes_suggestion() - { - var api = new RecordingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - client) as RdvViewModel; - - vm!.Address = "Saisie manuelle"; - vm.ApplyResolvedAddress("10 rue de Rivoli, 75001 Paris"); - - Assert.Equal("Saisie manuelle", vm.Address); - Assert.True(vm.HasSuggestedAddress); - Assert.Equal("10 rue de Rivoli, 75001 Paris", vm.SuggestedAddress); - - vm.ApplySuggestedAddressCommand.Execute(null); - - Assert.Equal("10 rue de Rivoli, 75001 Paris", vm.Address); - Assert.False(vm.HasSuggestedAddress); + Assert.Null(api.LastPath); + Assert.Contains("n'est pas encore pris en charge", vm.StatusMessage, StringComparison.OrdinalIgnoreCase); } [Fact] @@ -210,18 +70,19 @@ public class BillingCommandPageViewModelTests } }; var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 13, ActionName = "Brush", Title = "Coupe" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "brush", Name = "Brush" }, + var vm = new BillingCommandPageViewModel( + new ActivityBrowseItemDto { Code = "brush", Name = "Brush" }, new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" }, - client) as BrushViewModel; - vm!.EventDate = DateTime.Parse("2026-09-02 14:30"); - vm!.Address = "1 rue du Test"; - vm!.Latitude = 48.8566; - vm!.Longitude = 2.3522; - vm!.Consent = true; - vm!.AdditionalInfo = "Prévoir shampoing"; + new CommandFormSummaryDto { Id = 13, ActionName = "Brush", Title = "Coupe" }, + client) + { + EventDateText = "2026-09-02 14:30", + Address = "1 rue du Test", + LatitudeText = "48.8566", + LongitudeText = "2.3522", + Consent = true, + AdditionalInfo = "Prévoir shampoing", + }; await vm.InitializeAsync(); vm.SelectedPrestation = vm.AvailablePrestations[1]; @@ -245,22 +106,23 @@ public class BillingCommandPageViewModelTests } }; var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 14, ActionName = "MBrush", Title = "Coupe groupée" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "mbrush", Name = "MBrush" }, + var vm = new BillingCommandPageViewModel( + new ActivityBrowseItemDto { Code = "mbrush", Name = "MBrush" }, new ActivityUserDisplayItem { PerformerId = "perf-3", UserName = "Cara" }, - client) as MBrushViewModel; - vm!.EventDate = DateTime.Parse("2026-09-03 10:00"); - vm!.Address = "2 rue du Test"; - vm!.Latitude = 48.8567; - vm!.Longitude = 2.3523; - vm!.Consent = true; + new CommandFormSummaryDto { Id = 14, ActionName = "MBrush", Title = "Coupe groupée" }, + client) + { + EventDateText = "2026-09-03 10:00", + Address = "2 rue du Test", + LatitudeText = "48.8567", + LongitudeText = "2.3523", + Consent = true, + }; await vm.InitializeAsync(); - vm!.MultiPrestations[0].IsSelected = true; - vm!.MultiPrestations[1].IsSelected = true; - await vm!.SubmitCommand.ExecuteAsync(null); + vm.MultiPrestations[0].IsSelected = true; + vm.MultiPrestations[1].IsSelected = true; + await vm.SubmitCommand.ExecuteAsync(null); Assert.Equal("https://business.example/api/v1/billing/MBrush", api.LastPath); using var json = JsonDocument.Parse(JsonSerializer.Serialize(api.LastBody)); @@ -282,13 +144,13 @@ public class BillingCommandPageViewModelTests } }; var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 13, ActionName = "Brush", Title = "Coupe" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "brush", Name = "Brush" }, + var vm = new BillingCommandPageViewModel( + new ActivityBrowseItemDto { Code = "brush", Name = "Brush" }, new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" }, - client) as BrushViewModel; - await vm!.InitializeAsync(new BillingQueryDetailsDto + new CommandFormSummaryDto { Id = 13, ActionName = "Brush", Title = "Coupe" }, + client); + + await vm.InitializeAsync(new BillingQueryDetailsDto { Id = 77, BillingCode = "Brush", @@ -308,9 +170,9 @@ public class BillingCommandPageViewModelTests } }); - vm!.SelectedPrestation = vm!.AvailablePrestations[1]; - vm!.AdditionalInfo = "Note mise à jour"; - await vm!.SubmitCommand.ExecuteAsync(null); + vm.SelectedPrestation = vm.AvailablePrestations[1]; + vm.AdditionalInfo = "Note mise à jour"; + await vm.SubmitCommand.ExecuteAsync(null); Assert.Equal(HttpMethod.Put, api.LastMethod); Assert.Equal("https://business.example/api/v1/billing/Brush/77", api.LastPath); @@ -354,4 +216,4 @@ public class BillingCommandPageViewModelTests public ValueTask DisposeAsync() => ValueTask.CompletedTask; } -} +} \ No newline at end of file diff --git a/src/PostIt/PostIt.Tests/BillingQueriesPageViewModelTests.cs b/src/PostIt/PostIt.Tests/BillingQueriesPageViewModelTests.cs index d3d1d5694..a5d375323 100644 --- a/src/PostIt/PostIt.Tests/BillingQueriesPageViewModelTests.cs +++ b/src/PostIt/PostIt.Tests/BillingQueriesPageViewModelTests.cs @@ -14,9 +14,9 @@ public class BillingQueriesPageViewModelTests var api = new StubBillingApi(); var client = new BillingApiClient(api, "https://business.example/api/v1/"); var vm = new BillingQueriesPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, + new ActivityBrowseItemDto { Code = "dev", Name = "Développement" }, new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - new CommandFormSummary { Id = 1, ActionName = "Rdv", Title = "Rendez-vous" }, + new CommandFormSummaryDto { Id = 1, ActionName = "Rdv", Title = "Rendez-vous" }, client); await vm.InitializeAsync(); @@ -33,9 +33,9 @@ public class BillingQueriesPageViewModelTests var api = new StubBillingApi(); var client = new BillingApiClient(api, "https://business.example/api/v1/"); var vm = new BillingQueriesPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, + new ActivityBrowseItemDto { Code = "dev", Name = "Développement" }, new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - new CommandFormSummary { Id = 1, ActionName = "Rdv", Title = "Rendez-vous" }, + new CommandFormSummaryDto { Id = 1, ActionName = "Rdv", Title = "Rendez-vous" }, client, isReadOnly: true, ongoingOnly: true); @@ -45,8 +45,10 @@ public class BillingQueriesPageViewModelTests Assert.Equal(2, vm.Queries.Count); Assert.All(vm.Queries, q => Assert.DoesNotContain("Rejected", q.StatusLabel, StringComparison.OrdinalIgnoreCase)); Assert.Contains("lecture seule", vm.StatusMessage, StringComparison.OrdinalIgnoreCase); + Assert.False(vm.CanOpenDetails); - Assert.True(vm.Queries.Count > 0); + vm.SelectedQuery = vm.Queries[0]; + Assert.False(vm.OpenSelectedQueryCommand.CanExecute(null)); } private sealed class StubBillingApi : IYavscApiClient @@ -129,4 +131,4 @@ public class BillingQueriesPageViewModelTests public ValueTask DisposeAsync() => ValueTask.CompletedTask; } -} +} \ No newline at end of file diff --git a/src/PostIt/PostIt.Tests/NominatimReverseGeocodingServiceTests.cs b/src/PostIt/PostIt.Tests/NominatimReverseGeocodingServiceTests.cs deleted file mode 100644 index 4eceb7a2f..000000000 --- a/src/PostIt/PostIt.Tests/NominatimReverseGeocodingServiceTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Net; -using System.Text; -using PostIt.Services; - -namespace PostIt.Tests; - -public class NominatimReverseGeocodingServiceTests -{ - [Fact] - public async Task TryResolveAddressAsync_formats_compact_street_address_from_nominatim_payload() - { - var handler = new StubHandler(""" - { - "display_name": "6, Place de l'Hôtel-de-Ville - Esplanade de la Libération, Paris, 75004, France", - "address": { - "house_number": "6", - "road": "Place de l'Hôtel-de-Ville - Esplanade de la Libération", - "postcode": "75004", - "city": "Paris" - } - } - """); - - var service = new NominatimReverseGeocodingService(new HttpClient(handler) - { - BaseAddress = new Uri("https://nominatim.openstreetmap.org/") - }); - - var result = await service.TryResolveAddressAsync(48.8566, 2.3522); - - Assert.Equal("6, Place de l'Hôtel-de-Ville - Esplanade de la Libération, 75004, Paris", result); - Assert.NotNull(handler.LastRequest); - Assert.Contains("reverse?format=jsonv2", handler.LastRequest!.RequestUri!.ToString(), StringComparison.Ordinal); - } - - [Fact] - public async Task TryResolveAddressAsync_returns_null_on_unsuccessful_response() - { - var handler = new StubHandler("{}", HttpStatusCode.TooManyRequests); - var service = new NominatimReverseGeocodingService(new HttpClient(handler) - { - BaseAddress = new Uri("https://nominatim.openstreetmap.org/") - }); - - var result = await service.TryResolveAddressAsync(48.8566, 2.3522); - - Assert.Null(result); - } - - private sealed class StubHandler : HttpMessageHandler - { - private readonly string _payload; - private readonly HttpStatusCode _statusCode; - - public HttpRequestMessage? LastRequest { get; private set; } - - public StubHandler(string payload, HttpStatusCode statusCode = HttpStatusCode.OK) - { - _payload = payload; - _statusCode = statusCode; - } - - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - LastRequest = request; - return Task.FromResult(new HttpResponseMessage(_statusCode) - { - Content = new StringContent(_payload, Encoding.UTF8, "application/json") - }); - } - } -} \ No newline at end of file diff --git a/src/PostIt/PostIt.Tests/PostAclDialogTests.cs b/src/PostIt/PostIt.Tests/PostAclDialogTests.cs index e7175203c..dd2776291 100644 --- a/src/PostIt/PostIt.Tests/PostAclDialogTests.cs +++ b/src/PostIt/PostIt.Tests/PostAclDialogTests.cs @@ -95,7 +95,7 @@ public class PostAclDialogTests HttpMethod method, string path, object? body = null, CancellationToken ct = default) { - var absolute = new Uri(new Uri(Settings.ApiUrl), path); + var absolute = new Uri(new Uri(Settings.BusinessApiUrl), path); using var req = new HttpRequestMessage(method, absolute); using var resp = _http.SendAsync(req, ct).GetAwaiter().GetResult(); resp.EnsureSuccessStatusCode(); @@ -123,8 +123,8 @@ public class PostAclDialogTests var handler = new CountingHttpHandler(); var settings = new Settings(); var api = new TestableYavscApiClient(settings, new TokenStore(System.IO.Path.GetTempFileName()), handler); - var aclClient = new BlogAclApiClient(api, settings.ApiUrl); - var circleClient = new CircleApiClient(api, settings.ApiUrl); + var aclClient = new BlogAclApiClient(api, settings.BusinessApiUrl); + var circleClient = new CircleApiClient(api, settings.BusinessApiUrl); var services = new ServiceCollection(); services.AddSingleton(settings); diff --git a/src/PostIt/PostIt.Tests/RdvPageHeadlessTests.cs b/src/PostIt/PostIt.Tests/RdvPageHeadlessTests.cs deleted file mode 100644 index fdf8689ab..000000000 --- a/src/PostIt/PostIt.Tests/RdvPageHeadlessTests.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Headless.XUnit; -using PostIt.ViewModels; -using PostIt.ViewModels.Commands; -using PostIt.Views.Commands; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; - -namespace PostIt.Tests; - -public class RdvPageHeadlessTests -{ - [AvaloniaFact] - public void Suggested_address_panel_is_hidden_by_default() - { - var page = CreatePage(out _); - - var panel = page.FindControl("SuggestedAddressPanel"); - var progress = page.FindControl("SuggestedAddressProgress"); - - Assert.NotNull(panel); - Assert.NotNull(progress); - Assert.False(panel!.IsVisible); - Assert.False(progress!.IsVisible); - } - - [AvaloniaFact] - public async Task Suggested_address_panel_and_spinner_follow_viewmodel_state() - { - var page = CreatePage(out var vm); - var panel = page.FindControl("SuggestedAddressPanel")!; - var progress = page.FindControl("SuggestedAddressProgress")!; - var applyButton = page.FindControl