469 lines
20 KiB
C#
469 lines
20 KiB
C#
using System.Collections.ObjectModel;
|
|
using System.Text.Json;
|
|
using Omika.App.Local.Abstractions;
|
|
using Omika.App.Local.Models;
|
|
using Omika.App.Navigation;
|
|
using Omika.App.ViewModels;
|
|
using Omika.Core.Contracts.Documents;
|
|
using Omika.Core.Contracts.Mobile.Sync;
|
|
|
|
namespace Omika.Tests.Mobile.Local;
|
|
|
|
/// <summary>
|
|
/// Tests UX du ViewModel « Nouveau courriel » : MÊME moteur de saisie que le
|
|
/// Rapport (carrousel d'étapes interactives, UN/TAB en cartes, EXTRA, read-only
|
|
/// hors parcours) + conservation du corps HTML jusqu'à l'aperçu.
|
|
/// </summary>
|
|
public class NouveauEmailViewModelTests
|
|
{
|
|
private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web);
|
|
|
|
// =====================================================================
|
|
// Helpers
|
|
// =====================================================================
|
|
|
|
private static string ContactsJson(params (string Nom, string Role, string TelFixe)[] contacts)
|
|
{
|
|
var items = contacts.Select(c => new
|
|
{
|
|
id = Guid.NewGuid().ToString(),
|
|
nom = c.Nom,
|
|
role = c.Role,
|
|
telFixe = c.TelFixe,
|
|
telMobile = "06 00 00 00 00",
|
|
etage = "1"
|
|
});
|
|
return JsonSerializer.Serialize(items, Json);
|
|
}
|
|
|
|
private static string RdvJson(params (string dateDebut, string dateFin, string objet)[] rdvs)
|
|
{
|
|
var items = rdvs.Select(r => new
|
|
{
|
|
dateDebut = r.dateDebut,
|
|
dateFin = r.dateFin,
|
|
objet = r.objet,
|
|
note = string.Empty,
|
|
collaborateurs = "Jean Claude ROMERO"
|
|
});
|
|
return JsonSerializer.Serialize(items, Json);
|
|
}
|
|
|
|
private static RapportChampDto Champ(
|
|
string rubrique,
|
|
string? groupeApp = null,
|
|
string? membreMob = null,
|
|
string? type = "ChpSimpleChaine",
|
|
string? listeDonnesBD = null,
|
|
string? listeDonnesChoix = null,
|
|
bool modifiable = false,
|
|
string? nom = null)
|
|
=> new()
|
|
{
|
|
Id = "1",
|
|
Nom = nom,
|
|
Rubrique = rubrique,
|
|
Type = type,
|
|
GroupeApp = groupeApp,
|
|
MembreMob = membreMob,
|
|
ListeDonnesBD = listeDonnesBD,
|
|
ListeDonnesChoix = listeDonnesChoix,
|
|
Modifiable = modifiable
|
|
};
|
|
|
|
private static RapportChampDto TableauChamp(
|
|
string rubrique,
|
|
string groupeApp,
|
|
string listeDonnesBD,
|
|
params RapportChampSousElementDto[] membres)
|
|
=> new()
|
|
{
|
|
Id = "1",
|
|
Nom = "Tableau",
|
|
Rubrique = rubrique,
|
|
Type = "Tableau",
|
|
GroupeApp = groupeApp,
|
|
ListeDonnesBD = listeDonnesBD,
|
|
ListeMembre = membres
|
|
};
|
|
|
|
private static RapportChampSousElementDto SousElement(string rubrique, string? membreMob)
|
|
=> new() { Rubrique = rubrique, MembreMob = membreMob };
|
|
|
|
private static NouveauEmailViewModel CreerViewModel(
|
|
FakeInterventionService intervention,
|
|
FakeContactsService contacts,
|
|
FakeDocumentService documents)
|
|
=> new(intervention, contacts, documents, new NavigationService(), new FakeSyncEngine(), Omika.Core.Observability.NullTelemetryService.Instance);
|
|
|
|
// =====================================================================
|
|
// TEST 1-9 : moteur de saisie identique au Rapport
|
|
// =====================================================================
|
|
|
|
[Fact]
|
|
public void Simple_ReadOnly_PasDansEtapes_ValeurConservee()
|
|
{
|
|
var nomClient = new RapportChampEdition(Champ("$NOMCLIENT$", "INTER", "IN_NomClient", listeDonnesBD: "Smartis"));
|
|
var extra = new RapportChampEdition(Champ("$Saisie_Mob_1$", "EXTRA"));
|
|
|
|
var vm = CreerViewModel(new FakeInterventionService(), new FakeContactsService(), new FakeDocumentService());
|
|
vm.Rubriques = new ObservableCollection<RapportRubriqueResume>(
|
|
new[] { nomClient, extra }.Select(x => new RapportRubriqueResume(x)));
|
|
|
|
// « Nom du client » (Modifiable=0) ne fait PAS partie du parcours.
|
|
Assert.DoesNotContain(vm.EtapesInteractives, e => e.Champ == nomClient);
|
|
Assert.Same(extra, Assert.Single(vm.EtapesInteractives).Champ);
|
|
|
|
// …mais sa valeur reste disponible pour la génération ($NOMCLIENT$).
|
|
Assert.Equal("Smartis", nomClient.ToDto().ListeDonnesMob);
|
|
}
|
|
|
|
[Fact]
|
|
public void Simple_Modifiable_EstEtape()
|
|
{
|
|
var modifiable = new RapportChampEdition(Champ(
|
|
"$REFINTERNE$", "INTER", "IN_RefInterne", listeDonnesBD: "REF", modifiable: true));
|
|
|
|
var vm = CreerViewModel(new FakeInterventionService(), new FakeContactsService(), new FakeDocumentService());
|
|
vm.Rubriques = new ObservableCollection<RapportRubriqueResume>([new(modifiable)]);
|
|
|
|
Assert.Same(modifiable, Assert.Single(vm.EtapesInteractives).Champ);
|
|
}
|
|
|
|
[Fact]
|
|
public void Extra_EstEtape()
|
|
{
|
|
var extra = new RapportChampEdition(Champ("$Saisie_Mob_1$", "EXTRA"));
|
|
|
|
var vm = CreerViewModel(new FakeInterventionService(), new FakeContactsService(), new FakeDocumentService());
|
|
vm.Rubriques = new ObservableCollection<RapportRubriqueResume>([new(extra)]);
|
|
|
|
Assert.Same(extra, Assert.Single(vm.EtapesInteractives).Champ);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnRdv_TroisRubriques_UnSeulSelecteur()
|
|
{
|
|
string json = RdvJson(
|
|
("2022-08-31 08:00:00", "2022-08-31 17:00:00", "Maintenance"),
|
|
("2022-09-01 08:00:00", "2022-09-01 17:00:00", "Contrôle"),
|
|
("2022-09-15 08:00:00", "2022-09-15 09:00:00", "Visite"));
|
|
|
|
IReadOnlyList<RapportChampDto> champs =
|
|
[
|
|
Champ("$UNRDV.DATEDEBUT$", "RDV_INTER", "dateDebut", listeDonnesChoix: json),
|
|
Champ("$UNRDV.DATEDEBUT_D$", "RDV_INTER", "dateDebut_D", listeDonnesChoix: json),
|
|
Champ("$UNRDV.DATEFIN$", "RDV_INTER", "dateFin", listeDonnesChoix: json)
|
|
];
|
|
|
|
var intervention = new FakeInterventionService { Champs = champs };
|
|
var vm = CreerViewModel(intervention, new FakeContactsService(), new FakeDocumentService());
|
|
vm.InterventionGuid = Guid.NewGuid();
|
|
vm.ModeleSelectionne = new ModeleMailDto { Id = "1", Nom = "Modèle" };
|
|
|
|
// Les trois rubriques $UNRDV.* sont regroupées en UNE FAMILLE : un seul
|
|
// sélecteur (une seule entrée dans Rubriques, une seule étape interactive).
|
|
RapportRubriqueResume resume = Assert.Single(vm.Rubriques);
|
|
Assert.Equal(3, resume.Champ.MembresFamille.Count);
|
|
Assert.Single(vm.EtapesInteractives);
|
|
}
|
|
|
|
[Fact]
|
|
public void TabContact_MultiSelection_Cartes()
|
|
{
|
|
string json = ContactsJson(
|
|
("Jean Dupont", "Occupant", "04 11 22 33 44"),
|
|
("Marie Martin", "Responsable", "04 55 66 77 88"));
|
|
|
|
var edition = new RapportChampEdition(TableauChamp(
|
|
"$TABCONTACTSITE$", "CONTACT_SITE", json,
|
|
SousElement("$TABCTSITE.NOMPRENOM$", "saNom")));
|
|
|
|
Assert.True(edition.EstChoixMultiple);
|
|
Assert.Equal(2, edition.Options.Count);
|
|
|
|
edition.BasculerOptionCommand.Execute(edition.Options[0]);
|
|
edition.BasculerOptionCommand.Execute(edition.Options[1]);
|
|
Assert.Equal(2, edition.NombreSelectionnes);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnContact_SingleSelection_Cartes()
|
|
{
|
|
string json = ContactsJson(
|
|
("Jean Dupont", "Occupant", "04 11 22 33 44"),
|
|
("Marie Martin", "Responsable", "04 55 66 77 88"));
|
|
|
|
var edition = new RapportChampEdition(Champ(
|
|
"$UNCTSITE.NOMPRENOM$", "CONTACT_SITE", "saNom",
|
|
listeDonnesBD: "Jean Dupont", listeDonnesChoix: json));
|
|
|
|
Assert.True(edition.EstSelectionUnique);
|
|
Assert.Equal(1, edition.NombreSelectionnes);
|
|
|
|
// Clic sur une autre carte : l'ancienne sélection se replie, une seule cochée.
|
|
edition.BasculerOptionCommand.Execute(edition.Options[1]);
|
|
Assert.Equal(1, edition.NombreSelectionnes);
|
|
Assert.True(edition.Options[1].EstSelectionne);
|
|
Assert.False(edition.Options[0].EstSelectionne);
|
|
}
|
|
|
|
[Fact]
|
|
public void Modele100ReadOnly_ApercuDirect()
|
|
{
|
|
var a = new RapportChampEdition(Champ("$NOM$", "INTER", "IN_RefInterne", listeDonnesBD: "Nom"));
|
|
var b = new RapportChampEdition(Champ("$ADRESSE$", "INTER", "IN_RefInterne", listeDonnesBD: "Adresse"));
|
|
|
|
var vm = CreerViewModel(new FakeInterventionService(), new FakeContactsService(), new FakeDocumentService());
|
|
vm.Rubriques = new ObservableCollection<RapportRubriqueResume>(
|
|
new[] { a, b }.Select(x => new RapportRubriqueResume(x)));
|
|
|
|
Assert.Empty(vm.EtapesInteractives);
|
|
Assert.True(vm.AucuneEtapeInteractive);
|
|
}
|
|
|
|
[Fact]
|
|
public void Navigation_Carrousel_SauteLesReadOnly()
|
|
{
|
|
var a = new RapportChampEdition(Champ("$A$", "INTER", "IN_RefInterne", listeDonnesBD: "A"));
|
|
var b = new RapportChampEdition(Champ("$B$", "EXTRA"));
|
|
var c = new RapportChampEdition(Champ("$C$", "INTER", "IN_RefInterne", listeDonnesBD: "C"));
|
|
var d = new RapportChampEdition(Champ("$D$", "EXTRA"));
|
|
|
|
var vm = CreerViewModel(new FakeInterventionService(), new FakeContactsService(), new FakeDocumentService());
|
|
vm.Rubriques = new ObservableCollection<RapportRubriqueResume>(
|
|
new[] { a, b, c, d }.Select(x => new RapportRubriqueResume(x)));
|
|
|
|
// Ouvre la première étape interactive (B, la A read-only est sautée).
|
|
vm.OuvrirRubriqueCommand.Execute(vm.EtapesInteractives[0]);
|
|
Assert.Same(b, vm.RubriqueSelectionnee);
|
|
|
|
// Suivant → D (saute C read-only).
|
|
vm.RubriqueSuivanteCommand.Execute(null);
|
|
Assert.Same(d, vm.RubriqueSelectionnee);
|
|
|
|
// Précédent → B.
|
|
vm.RubriquePrecedenteCommand.Execute(null);
|
|
Assert.Same(b, vm.RubriqueSelectionnee);
|
|
|
|
// Retour à la liste des rubriques.
|
|
vm.RetourRubriquesCommand.Execute(null);
|
|
Assert.False(vm.AfficheDetail);
|
|
Assert.True(vm.AfficheListe);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Continuer_PayloadContientTousLesChamps()
|
|
{
|
|
var nomClient = Champ("$NOMCLIENT$", "INTER", "IN_NomClient", listeDonnesBD: "Smartis");
|
|
var extra = Champ("$Saisie_Mob_1$", "EXTRA");
|
|
var intervention = new FakeInterventionService
|
|
{
|
|
Champs = [nomClient, extra],
|
|
Apercu = new EmailModeleApercuDto { Sujet = "Rapport intervention 42", Corps = "<p>Bonjour Smartis</p>" }
|
|
};
|
|
|
|
var vm = CreerViewModel(intervention, new FakeContactsService(), new FakeDocumentService());
|
|
vm.InterventionGuid = Guid.NewGuid();
|
|
vm.ModeleSelectionne = new ModeleMailDto { Id = "1", Nom = "Modèle" };
|
|
|
|
await vm.ContinuerCommand.ExecuteAsync(null);
|
|
|
|
// Le payload envoyé au serveur contient TOUS les champs (read-only inclus).
|
|
Assert.NotNull(intervention.LastApercuRequest);
|
|
Assert.Equal(2, intervention.LastApercuRequest!.ListeMob.Count);
|
|
Assert.Contains(intervention.LastApercuRequest.ListeMob, c => c.Rubrique == "$NOMCLIENT$" && c.ListeDonnesMob == "Smartis");
|
|
Assert.Equal(2, vm.Etape);
|
|
}
|
|
|
|
// =====================================================================
|
|
// TEST HTML 1-5 : conservation du corps HTML
|
|
// =====================================================================
|
|
|
|
[Fact]
|
|
public void Html_CorpsHtml_ConserveIntegralementDansViewModel()
|
|
{
|
|
var vm = CreerViewModel(new FakeInterventionService(), new FakeContactsService(), new FakeDocumentService());
|
|
|
|
vm.Corps = "<p>Bonjour</p>";
|
|
|
|
Assert.Equal("<p>Bonjour</p>", vm.Corps);
|
|
Assert.True(vm.CorpsContientHtml);
|
|
Assert.Equal("Bonjour", vm.CorpsApercuTexte);
|
|
}
|
|
|
|
[Fact]
|
|
public void Html_Tableau_NonConvertiEnTexteSepareParPipes()
|
|
{
|
|
var vm = CreerViewModel(new FakeInterventionService(), new FakeContactsService(), new FakeDocumentService());
|
|
|
|
string html = "<table><tr><td>Moreau Valérie</td><td>04 13 95 46 73</td></tr></table>";
|
|
vm.Corps = html;
|
|
|
|
// La SOURCE reste le HTML intégral : jamais « Moreau Valérie | … ».
|
|
Assert.Equal(html, vm.Corps);
|
|
// L'aperçu visuel contient bien la valeur, séparée par des espaces (pas des pipes).
|
|
Assert.Contains("Moreau Valérie", vm.CorpsApercuTexte);
|
|
Assert.DoesNotContain("<table>", vm.CorpsApercuTexte);
|
|
}
|
|
|
|
[Fact]
|
|
public void Html_AucunDecodeSurLaSource()
|
|
{
|
|
var vm = CreerViewModel(new FakeInterventionService(), new FakeContactsService(), new FakeDocumentService());
|
|
|
|
vm.Corps = "<p>R&D & Cie</p>";
|
|
|
|
// La source conserve les entités HTML (aucun HtmlDecode / StripTags).
|
|
Assert.Equal("<p>R&D & Cie</p>", vm.Corps);
|
|
// Seul l'aperçu visuel décode.
|
|
Assert.Equal("R&D & Cie", vm.CorpsApercuTexte);
|
|
}
|
|
|
|
[Fact]
|
|
public void Html_SujetResteTexteSimple()
|
|
{
|
|
var vm = CreerViewModel(new FakeInterventionService(), new FakeContactsService(), new FakeDocumentService());
|
|
|
|
vm.Sujet = "Rapport intervention 391244";
|
|
|
|
// Le sujet est un string : aucun contrôle HTML, aucun rendu (seul le Corps l'est).
|
|
Assert.Equal("Rapport intervention 391244", vm.Sujet);
|
|
Assert.False(vm.CorpsContientHtml);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Html_Continuer_ConserveLeHtmlDuServeur()
|
|
{
|
|
var intervention = new FakeInterventionService
|
|
{
|
|
Champs = [Champ("$NOMCLIENT$", "INTER", "IN_NomClient", listeDonnesBD: "DigiNova")],
|
|
Apercu = new EmailModeleApercuDto
|
|
{
|
|
Sujet = "Rapport intervention 391244",
|
|
Corps = "<p>Bonjour DigiNova</p><table><tr><td>Moreau Valérie</td></tr></table>"
|
|
}
|
|
};
|
|
|
|
var vm = CreerViewModel(intervention, new FakeContactsService(), new FakeDocumentService());
|
|
vm.InterventionGuid = Guid.NewGuid();
|
|
vm.ModeleSelectionne = new ModeleMailDto { Id = "1", Nom = "Modèle" };
|
|
|
|
await vm.ContinuerCommand.ExecuteAsync(null);
|
|
|
|
// Sujet = texte ; Corps = HTML intégral (même moteur aperçu/création côté serveur).
|
|
Assert.Equal("Rapport intervention 391244", vm.Sujet);
|
|
Assert.Equal("<p>Bonjour DigiNova</p><table><tr><td>Moreau Valérie</td></tr></table>", vm.Corps);
|
|
Assert.True(vm.CorpsContientHtml);
|
|
Assert.Contains("Moreau Valérie", vm.CorpsApercuTexte);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Enregistrer_EnvoieCorpsHtml_PasTexteAplati()
|
|
{
|
|
var intervention = new FakeInterventionService();
|
|
var vm = CreerViewModel(intervention, new FakeContactsService(), new FakeDocumentService());
|
|
vm.InterventionGuid = Guid.NewGuid();
|
|
vm.Sujet = "Rapport";
|
|
vm.Corps = "<p>Bonjour</p><table><tr><td>30/01/2026</td></tr></table>";
|
|
|
|
await vm.EnregistrerCommand.ExecuteAsync(null);
|
|
|
|
// La source envoyée est le HTML intégral (Corps), jamais l'aperçu texte aplati.
|
|
Assert.NotNull(intervention.LastEmailRequest);
|
|
Assert.Equal(vm.Corps, intervention.LastEmailRequest!.Message);
|
|
Assert.NotEqual(vm.CorpsApercuTexte, intervention.LastEmailRequest!.Message);
|
|
}
|
|
|
|
// =====================================================================
|
|
// Fakes
|
|
// =====================================================================
|
|
|
|
private sealed class FakeInterventionService : IInterventionLocalService
|
|
{
|
|
public IReadOnlyList<RapportChampDto> Champs { get; set; } = [];
|
|
public EmailModeleApercuDto? Apercu { get; set; }
|
|
public CreateEmailModeleRequest? LastApercuRequest { get; private set; }
|
|
|
|
public Task<IReadOnlyList<RapportChampDto>> GetMailChampsAsync(
|
|
GetRapportChampsRequest request, CancellationToken ct = default) => Task.FromResult(Champs);
|
|
|
|
public Task<EmailModeleApercuDto?> GetEmailModeleApercuAsync(
|
|
CreateEmailModeleRequest request, CancellationToken ct = default)
|
|
{
|
|
LastApercuRequest = request;
|
|
return Task.FromResult(Apercu);
|
|
}
|
|
|
|
public Task<IReadOnlyList<SmtpConfigurationDto>> GetSmtpConfigurationsAsync(
|
|
CancellationToken ct = default) => Task.FromResult<IReadOnlyList<SmtpConfigurationDto>>([]);
|
|
|
|
public CreateEmailInterventionRequest? LastEmailRequest { get; private set; }
|
|
|
|
public Task<EmailInterventionDto> AddEmailAsync(
|
|
Guid interventionGuid, CreateEmailInterventionRequest request, CancellationToken ct = default)
|
|
{
|
|
LastEmailRequest = request;
|
|
return Task.FromResult(new EmailInterventionDto { Id = Guid.NewGuid().ToString() });
|
|
}
|
|
|
|
// Membres abstraits non utilisés par ces tests.
|
|
public Task<Guid> CreateAsync(InterventionSyncPayload payload, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<Guid> UpdateAsync(Guid interventionGuid, InterventionSyncPayload payload, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<Guid> ArchiveAsync(Guid interventionGuid, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<InterventionDetail?> GetInterventionByIdAsync(Guid interventionGuid, CancellationToken ct = default)
|
|
=> Task.FromResult<InterventionDetail?>(null);
|
|
public Task<IReadOnlyList<InterventionListItem>> GetInterventionsAsync(CancellationToken ct = default)
|
|
=> Task.FromResult<IReadOnlyList<InterventionListItem>>([]);
|
|
}
|
|
|
|
private sealed class FakeContactsService : IInterventionContactsService
|
|
{
|
|
public Task<IReadOnlyList<InterventionContact>> GetContactsForInterventionAsync(
|
|
Guid interventionGuid, CancellationToken ct = default) => Task.FromResult<IReadOnlyList<InterventionContact>>([]);
|
|
|
|
public Task<InterventionContact?> GetContactByIdAsync(
|
|
Guid contactGuid, CancellationToken ct = default) => Task.FromResult<InterventionContact?>(null);
|
|
|
|
public Task<IReadOnlyList<InterventionContact>> GetContactsDisponiblesAsync(
|
|
Guid interventionGuid, CancellationToken ct = default) => Task.FromResult<IReadOnlyList<InterventionContact>>([]);
|
|
}
|
|
|
|
private sealed class FakeDocumentService : IDocumentLocalService
|
|
{
|
|
public Task<IReadOnlyList<InterventionDocumentItem>> GetDocumentsForInterventionAsync(
|
|
Guid interventionGuid, CancellationToken ct = default) => Task.FromResult<IReadOnlyList<InterventionDocumentItem>>([]);
|
|
|
|
public Task<bool> EnsureDownloadedAsync(Guid documentGuid, CancellationToken ct = default) => Task.FromResult(true);
|
|
public Task<bool> OpenAsync(Guid documentGuid, CancellationToken ct = default) => Task.FromResult(true);
|
|
public Task<string?> GetCachedFilePathAsync(Guid documentGuid, CancellationToken ct = default) => Task.FromResult<string?>(null);
|
|
public Task<IReadOnlyList<DocumentNoteItem>> GetNotesForDocumentAsync(Guid documentGuid, CancellationToken ct = default)
|
|
=> Task.FromResult<IReadOnlyList<DocumentNoteItem>>([]);
|
|
public Task<DocumentNoteItem> AddNoteAsync(Guid documentGuid, string contenu, CancellationToken ct = default)
|
|
=> Task.FromResult(new DocumentNoteItem());
|
|
public Task<InterventionDocumentItem?> AddPhotoAsync(Guid interventionGuid, CancellationToken ct = default)
|
|
=> Task.FromResult<InterventionDocumentItem?>(null);
|
|
public Task<AddPhotosResult> AddPhotosAsync(Guid interventionGuid, CancellationToken ct = default)
|
|
=> Task.FromResult(new AddPhotosResult());
|
|
public Task<InterventionDocumentItem?> AddPhotoFromCameraAsync(Guid interventionGuid, CancellationToken ct = default)
|
|
=> Task.FromResult<InterventionDocumentItem?>(null);
|
|
public Task<InterventionDocumentItem?> AddDocumentAsync(Guid interventionGuid, CancellationToken ct = default)
|
|
=> Task.FromResult<InterventionDocumentItem?>(null);
|
|
public Task<InterventionDocumentItem?> AddAnnotatedPhotoAsync(
|
|
Guid interventionGuid, Guid sourceDocumentGuid, byte[] pngBytes, string fileName, CancellationToken ct = default)
|
|
=> Task.FromResult<InterventionDocumentItem?>(null);
|
|
}
|
|
|
|
private sealed class FakeSyncEngine : ISyncEngine
|
|
{
|
|
public Task<SyncCycleResult> SyncAsync(CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(new SyncCycleResult());
|
|
|
|
public Task<SyncCycleResult> PushAsync(CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(new SyncCycleResult());
|
|
}
|
|
}
|