yavsc/src/PostIt/PostIt.Tests/BillingCommandPageViewModelTests.cs

338 lines
14 KiB
C#
Raw Normal View History

2026-08-31 03:37:09 +01:00
using System.Text.Json;
2026-09-04 13:19:31 +01:00
using PostIt.Helpers;
2026-09-02 14:41:05 +01:00
using PostIt.Services;
2026-08-31 03:37:09 +01:00
using PostIt.ViewModels;
2026-09-04 13:19:31 +01:00
using PostIt.ViewModels.Commands;
2026-08-31 03:37:09 +01:00
using Yavsc;
using Yavsc.Abstract.Workflow;
using Yavsc.Api.Client;
2026-08-31 04:15:10 +01:00
using Yavsc.Models.Haircut;
2026-08-31 03:37:09 +01:00
namespace PostIt.Tests;
public class BillingCommandPageViewModelTests
{
[Fact]
public async Task SubmitAsync_posts_rdv_payload_to_selected_billing_route()
{
var api = new RecordingApi();
var client = new BillingApiClient(api, "https://business.example/api/v1/");
2026-09-04 13:19:31 +01:00
var vm =
new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" }
.CreateCommandPageViewModel(
2026-09-02 14:52:17 +01:00
new ActivityInfo { Code = "dev", Name = "Développement" },
2026-08-31 03:37:09 +01:00
new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" },
2026-09-04 13:19:31 +01:00
client) as RdvViewModel;
2026-09-04 14:53:00 +01:00
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;
2026-08-31 03:37:09 +01:00
await vm.SubmitCommand.ExecuteAsync(null);
Assert.Equal("https://business.example/api/v1/billing/Rdv", api.LastPath);
Assert.NotNull(api.LastBody);
using var json = JsonDocument.Parse(JsonSerializer.Serialize(api.LastBody));
Assert.Equal("dev", json.RootElement.GetProperty("ActivityCode").GetString());
Assert.Equal("perf-1", json.RootElement.GetProperty("PerformerId").GetString());
Assert.Equal("Point de cadrage", json.RootElement.GetProperty("Reason").GetString());
Assert.Equal((int)QueryStatus.Inserted, json.RootElement.GetProperty("Status").GetInt32());
}
[Fact]
public async Task SubmitAsync_refuses_unsupported_billing_code()
{
var api = new RecordingApi();
var client = new BillingApiClient(api, "https://business.example/api/v1/");
2026-09-04 13:19:31 +01:00
var vm =
new CommandFormSummary { Id = 13, ActionName = "Book", Title = "Réservation" }
.CreateCommandPageViewModel(
2026-09-02 14:52:17 +01:00
new ActivityInfo { Code = "book", Name = "Book" },
2026-08-31 03:37:09 +01:00
new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" },
client);
2026-09-04 14:53:00 +01:00
Assert.Null(vm);
2026-08-31 03:37:09 +01:00
}
2026-09-02 14:41:05 +01:00
[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/");
2026-09-04 13:19:31 +01:00
var vm =
new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" }
.CreateCommandPageViewModel(
2026-09-02 14:52:17 +01:00
new ActivityInfo { Code = "dev", Name = "Développement" },
2026-09-02 14:41:05 +01:00
new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" },
2026-09-04 13:19:31 +01:00
client) as RdvViewModel;
2026-09-04 14:53:00 +01:00
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;
2026-09-02 14:41:05 +01:00
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/");
2026-09-04 13:19:31 +01:00
var vm =
new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" }
.CreateCommandPageViewModel(
2026-09-02 14:52:17 +01:00
new ActivityInfo { Code = "dev", Name = "Développement" },
2026-09-02 14:41:05 +01:00
new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" },
2026-09-04 13:19:31 +01:00
client) as RdvViewModel;
2026-09-02 14:41:05 +01:00
2026-09-04 14:53:00 +01:00
await vm!.UseCurrentLocationCommand.ExecuteAsync(null);
2026-09-02 14:41:05 +01:00
2026-09-04 14:53:00 +01:00
Assert.Equal(48.8566, vm!.Latitude);
Assert.Equal(2.3522, vm!.Longitude);
2026-09-02 14:41:05 +01:00
}
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 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);
}
2026-08-31 04:15:10 +01:00
[Fact]
public async Task InitializeAsync_loads_prestations_for_brush_and_submit_posts_selected_prestation()
{
var api = new RecordingApi
{
HairPrestations = new List<HairPrestationDto>
{
new() { Id = 10, Title = "Femme · Cheveux mi-longs", Details = "Coupe · Brushing" },
new() { Id = 11, Title = "Homme · Cheveux courts", Details = "Coupe · Coiffage" },
}
};
var client = new BillingApiClient(api, "https://business.example/api/v1/");
2026-09-04 13:19:31 +01:00
var vm =
new CommandFormSummary { Id = 13, ActionName = "Brush", Title = "Coupe" }
.CreateCommandPageViewModel(
2026-09-02 14:52:17 +01:00
new ActivityInfo { Code = "brush", Name = "Brush" },
2026-08-31 04:15:10 +01:00
new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" },
2026-09-04 13:19:31 +01:00
client) as BrushViewModel;
2026-09-04 14:53:00 +01:00
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";
2026-08-31 04:15:10 +01:00
await vm.InitializeAsync();
vm.SelectedPrestation = vm.AvailablePrestations[1];
await vm.SubmitCommand.ExecuteAsync(null);
Assert.Equal("https://business.example/api/v1/billing/Brush", api.LastPath);
using var json = JsonDocument.Parse(JsonSerializer.Serialize(api.LastBody));
Assert.Equal(11, json.RootElement.GetProperty("PrestationId").GetInt32());
Assert.Equal("Prévoir shampoing", json.RootElement.GetProperty("AdditionalInfo").GetString());
}
[Fact]
public async Task InitializeAsync_loads_prestations_for_mbrush_and_submit_posts_selected_prestations()
{
var api = new RecordingApi
{
HairPrestations = new List<HairPrestationDto>
{
new() { Id = 21, Title = "Femme · Cheveux longs", Details = "Coupe · Couleur" },
new() { Id = 22, Title = "Enfant · Cheveux courts", Details = "Coupe · Sans technique" },
}
};
var client = new BillingApiClient(api, "https://business.example/api/v1/");
2026-09-04 13:19:31 +01:00
var vm =
new CommandFormSummary { Id = 14, ActionName = "MBrush", Title = "Coupe groupée" }
.CreateCommandPageViewModel(
2026-09-02 14:52:17 +01:00
new ActivityInfo { Code = "mbrush", Name = "MBrush" },
2026-08-31 04:15:10 +01:00
new ActivityUserDisplayItem { PerformerId = "perf-3", UserName = "Cara" },
2026-09-04 14:53:00 +01:00
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;
2026-08-31 04:15:10 +01:00
await vm.InitializeAsync();
2026-09-04 14:53:00 +01:00
vm!.MultiPrestations[0].IsSelected = true;
vm!.MultiPrestations[1].IsSelected = true;
await vm!.SubmitCommand.ExecuteAsync(null);
2026-08-31 04:15:10 +01:00
Assert.Equal("https://business.example/api/v1/billing/MBrush", api.LastPath);
using var json = JsonDocument.Parse(JsonSerializer.Serialize(api.LastBody));
var prestations = json.RootElement.GetProperty("Prestations");
Assert.Equal(2, prestations.GetArrayLength());
Assert.Equal(21, prestations[0].GetProperty("PrestationId").GetInt32());
Assert.Equal(22, prestations[1].GetProperty("PrestationId").GetInt32());
}
2026-08-31 04:27:39 +01:00
[Fact]
public async Task InitializeAsync_with_existing_brush_query_prefills_and_submit_updates_query()
{
var api = new RecordingApi
{
HairPrestations = new List<HairPrestationDto>
{
new() { Id = 30, Title = "Femme · Cheveux longs", Details = "Coupe · Brushing" },
new() { Id = 31, Title = "Homme · Cheveux courts", Details = "Coupe" },
}
};
var client = new BillingApiClient(api, "https://business.example/api/v1/");
2026-09-04 13:19:31 +01:00
var vm =
new CommandFormSummary { Id = 13, ActionName = "Brush", Title = "Coupe" }
.CreateCommandPageViewModel(
2026-09-02 14:52:17 +01:00
new ActivityInfo { Code = "brush", Name = "Brush" },
2026-08-31 04:27:39 +01:00
new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" },
2026-09-04 13:19:31 +01:00
client) as BrushViewModel;
2026-09-04 14:53:00 +01:00
await vm!.InitializeAsync(new BillingQueryDetailsDto
2026-08-31 04:27:39 +01:00
{
Id = 77,
BillingCode = "Brush",
ActivityCode = "brush",
PerformerId = "perf-2",
ClientId = "cli-1",
EventDate = new DateTime(2026, 9, 2, 14, 30, 0, DateTimeKind.Utc),
Consent = true,
Status = QueryStatus.Accepted,
PrestationId = 30,
AdditionalInfo = "Ancienne note",
Location = new BillingLocationDto
{
Address = "1 rue du Test",
Latitude = 48.8566,
Longitude = 2.3522,
}
});
2026-09-04 14:53:00 +01:00
vm!.SelectedPrestation = vm!.AvailablePrestations[1];
vm!.AdditionalInfo = "Note mise à jour";
await vm!.SubmitCommand.ExecuteAsync(null);
2026-08-31 04:27:39 +01:00
Assert.Equal(HttpMethod.Put, api.LastMethod);
Assert.Equal("https://business.example/api/v1/billing/Brush/77", api.LastPath);
Assert.True(vm.IsEditingExisting);
Assert.Equal("Mettre à jour la commande", vm.SubmitLabel);
using var json = JsonDocument.Parse(JsonSerializer.Serialize(api.LastBody));
Assert.Equal(77, json.RootElement.GetProperty("Id").GetInt32());
Assert.Equal(31, json.RootElement.GetProperty("PrestationId").GetInt32());
Assert.Equal("Note mise à jour", json.RootElement.GetProperty("AdditionalInfo").GetString());
Assert.Equal((int)QueryStatus.Accepted, json.RootElement.GetProperty("Status").GetInt32());
}
2026-08-31 03:37:09 +01:00
private sealed class RecordingApi : IYavscApiClient
{
public HttpClient Http { get; } = new();
2026-08-31 04:27:39 +01:00
public HttpMethod? LastMethod { get; private set; }
2026-08-31 03:37:09 +01:00
public string? LastPath { get; private set; }
public object? LastBody { get; private set; }
2026-08-31 04:15:10 +01:00
public List<HairPrestationDto>? HairPrestations { get; init; }
2026-08-31 03:37:09 +01:00
public Task<T> CallAsync<T>(HttpMethod method, string path, object? body = null, CancellationToken ct = default)
{
2026-08-31 04:27:39 +01:00
LastMethod = method;
2026-08-31 03:37:09 +01:00
LastPath = path;
LastBody = body;
2026-08-31 04:15:10 +01:00
if (typeof(T) == typeof(List<HairPrestationDto>))
{
return Task.FromResult((T)(object)(HairPrestations ?? new List<HairPrestationDto>()));
}
2026-08-31 03:37:09 +01:00
return Task.FromResult(default(T)!);
}
public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default)
{
2026-08-31 04:27:39 +01:00
LastMethod = method;
2026-08-31 03:37:09 +01:00
LastPath = path;
LastBody = body;
return Task.CompletedTask;
}
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}
2026-09-02 14:41:05 +01:00
}