515 lines
21 KiB
C#
515 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Omika.App.Local.Abstractions;
|
|
using Omika.App.Local.Models;
|
|
using Omika.App.Navigation;
|
|
using Omika.App.Services;
|
|
using Omika.App.ViewModels;
|
|
using Omika.Core.Contracts.Documents;
|
|
using Omika.Core.Contracts.Mobile.Sync;
|
|
using Xunit;
|
|
|
|
namespace Omika.Tests.Mobile.Local;
|
|
|
|
/// <summary>
|
|
/// Messagerie EXTERNE dans la création d'un courriel : « Envoyer » ouvre la
|
|
/// messagerie du téléphone avec le courriel pré-rempli (aucun envoi par Omika),
|
|
/// puis la réponse de l'utilisateur détermine l'état enregistré :
|
|
/// <list type="bullet">
|
|
/// <item>« Oui » → courriel ENVOYÉ (<c>ME_EnvoyerMob</c> côté serveur) ;</item>
|
|
/// <item>« Non » (ou retour sans confirmation) → BROUILLON.</item>
|
|
/// </list>
|
|
/// La messagerie Back-Office (interne) n'est pas affectée : l'envoi SMTP par le
|
|
/// serveur reste le seul chemin.
|
|
/// </summary>
|
|
public class NouveauEmailExterneTests
|
|
{
|
|
// =====================================================================
|
|
// Construction
|
|
// =====================================================================
|
|
|
|
private static NouveauEmailViewModel Creer(
|
|
FakeInterventionService intervention,
|
|
FakeEmailLauncher launcher,
|
|
IAppRetourService? retour,
|
|
FakePermissionService permissions,
|
|
FakeDocumentService? documents = null)
|
|
{
|
|
var viewModel = new NouveauEmailViewModel(
|
|
intervention,
|
|
new FakeContactsService(),
|
|
documents ?? new FakeDocumentService(),
|
|
new NavigationService(),
|
|
new FakeSyncEngine(),
|
|
Omika.Core.Observability.NullTelemetryService.Instance,
|
|
launcher,
|
|
retour,
|
|
permissions);
|
|
|
|
viewModel.InterventionGuid = Guid.NewGuid();
|
|
viewModel.Sujet = "Intervention du 12";
|
|
viewModel.Corps = "<p>Bonjour,</p><p>Compte rendu ci-joint.</p>";
|
|
viewModel.Destinataires = "client@exemple.fr";
|
|
viewModel.Cc = "copie@exemple.fr";
|
|
return viewModel;
|
|
}
|
|
|
|
private static (NouveauEmailViewModel Vm, FakeInterventionService Intervention, FakeEmailLauncher Launcher) CreerExterne(
|
|
IAppRetourService? retour = null,
|
|
FakeDocumentService? documents = null)
|
|
{
|
|
var intervention = new FakeInterventionService();
|
|
var launcher = new FakeEmailLauncher();
|
|
NouveauEmailViewModel viewModel = Creer(
|
|
intervention,
|
|
launcher,
|
|
retour ?? new FakeAppRetourService(),
|
|
new FakePermissionService { MessagerieExterneValue = true },
|
|
documents);
|
|
return (viewModel, intervention, launcher);
|
|
}
|
|
|
|
// =====================================================================
|
|
// Messagerie interne : rien ne change
|
|
// =====================================================================
|
|
|
|
/// <summary>Back-Office : l'envoi passe par le SERVEUR (SMTP), aucune messagerie externe.</summary>
|
|
[Fact]
|
|
public async Task Envoyer_BackOffice_EnvoieParLeServeurSansMessagerieExterne()
|
|
{
|
|
var intervention = new FakeInterventionService();
|
|
var launcher = new FakeEmailLauncher();
|
|
NouveauEmailViewModel vm = Creer(
|
|
intervention,
|
|
launcher,
|
|
new FakeAppRetourService(),
|
|
new FakePermissionService { MessagerieExterneValue = false });
|
|
|
|
vm.ConfigurationSmtpSelectionnee = new SmtpConfigurationDto { Id = "cfg-1", Email = "moi@exemple.fr" };
|
|
|
|
await vm.EnvoyerCommand.ExecuteAsync(null);
|
|
|
|
Assert.Equal(1, intervention.EnvoisInternes);
|
|
Assert.Equal(0, launcher.Appels);
|
|
Assert.False(vm.AfficherQuestionEnvoiExterne);
|
|
}
|
|
|
|
/// <summary>Back-Office : la configuration SMTP reste proposée (comportement inchangé).</summary>
|
|
[Fact]
|
|
public void BackOffice_AfficheLaConfigurationSmtp()
|
|
{
|
|
var intervention = new FakeInterventionService();
|
|
NouveauEmailViewModel vm = Creer(
|
|
intervention,
|
|
new FakeEmailLauncher(),
|
|
new FakeAppRetourService(),
|
|
new FakePermissionService { MessagerieExterneValue = false });
|
|
|
|
Assert.True(vm.AfficherConfigurationSmtp);
|
|
Assert.False(vm.MessagerieExterne);
|
|
}
|
|
|
|
// =====================================================================
|
|
// Messagerie externe : ouverture + question
|
|
// =====================================================================
|
|
|
|
/// <summary>
|
|
/// Externe : « Envoyer » ouvre la messagerie avec le courriel pré-rempli,
|
|
/// attend le retour de l'utilisateur puis pose la question. Rien n'est encore
|
|
/// enregistré à ce stade.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Envoyer_Externe_OuvreLaMessageriePuisDemandeConfirmation()
|
|
{
|
|
(NouveauEmailViewModel vm, FakeInterventionService intervention, FakeEmailLauncher launcher) = CreerExterne();
|
|
|
|
await vm.EnvoyerCommand.ExecuteAsync(null);
|
|
|
|
Assert.Equal(1, launcher.Appels);
|
|
Assert.NotNull(launcher.DerniereDemande);
|
|
Assert.Equal(new[] { "client@exemple.fr" }, launcher.DerniereDemande!.Destinataires);
|
|
Assert.Equal(new[] { "copie@exemple.fr" }, launcher.DerniereDemande.Cc);
|
|
Assert.Equal("Intervention du 12", launcher.DerniereDemande.Sujet);
|
|
|
|
// Le client email ne rend pas le HTML : le corps transmis est du TEXTE.
|
|
Assert.DoesNotContain('<', launcher.DerniereDemande.CorpsTexte);
|
|
Assert.Contains("Compte rendu ci-joint.", launcher.DerniereDemande.CorpsTexte);
|
|
|
|
Assert.True(vm.AfficherQuestionEnvoiExterne);
|
|
Assert.False(vm.AfficherConfigurationSmtp);
|
|
Assert.Equal(0, intervention.EnvoisInternes);
|
|
Assert.Equal(0, intervention.EnregistrementsEnvoyes);
|
|
Assert.Equal(0, intervention.EnregistrementsBrouillon);
|
|
}
|
|
|
|
/// <summary>« Oui » : le courriel est enregistré ENVOYÉ (ME_EnvoyerMob = vrai côté serveur).</summary>
|
|
[Fact]
|
|
public async Task RepondreOui_EnregistreLeCourrielEnvoye()
|
|
{
|
|
(NouveauEmailViewModel vm, FakeInterventionService intervention, _) = CreerExterne();
|
|
|
|
await vm.EnvoyerCommand.ExecuteAsync(null);
|
|
await vm.RepondreEnvoiExterneOuiCommand.ExecuteAsync(null);
|
|
|
|
Assert.Equal(1, intervention.EnregistrementsEnvoyes);
|
|
Assert.Equal(0, intervention.EnregistrementsBrouillon);
|
|
Assert.Equal(0, intervention.EnvoisInternes);
|
|
Assert.False(vm.AfficherQuestionEnvoiExterne);
|
|
|
|
CreateEmailInterventionRequest request = Assert.Single(intervention.DemandesEnvoyees);
|
|
Assert.Equal("Intervention du 12", request.Objet);
|
|
// Le corps enregistré reste le HTML d'origine (seul l'aperçu est aplati).
|
|
Assert.Equal(vm.Corps, request.Message);
|
|
}
|
|
|
|
/// <summary>« Non » : le courriel est enregistré en BROUILLON, jamais comme envoyé.</summary>
|
|
[Fact]
|
|
public async Task RepondreNon_EnregistreUnBrouillon()
|
|
{
|
|
(NouveauEmailViewModel vm, FakeInterventionService intervention, _) = CreerExterne();
|
|
|
|
await vm.EnvoyerCommand.ExecuteAsync(null);
|
|
await vm.RepondreEnvoiExterneNonCommand.ExecuteAsync(null);
|
|
|
|
Assert.Equal(0, intervention.EnregistrementsEnvoyes);
|
|
Assert.Equal(1, intervention.EnregistrementsBrouillon);
|
|
Assert.False(vm.AfficherQuestionEnvoiExterne);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Aucune application de messagerie : un message clair, et surtout RIEN n'est
|
|
/// enregistré (ni envoyé, ni brouillon).
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Envoyer_Externe_SansMessagerieDisponible_RienNEstEnregistre()
|
|
{
|
|
var intervention = new FakeInterventionService();
|
|
var launcher = new FakeEmailLauncher { Resultat = EmailExterneResultat.Echec };
|
|
NouveauEmailViewModel vm = Creer(
|
|
intervention,
|
|
launcher,
|
|
new FakeAppRetourService(),
|
|
new FakePermissionService { MessagerieExterneValue = true });
|
|
|
|
await vm.EnvoyerCommand.ExecuteAsync(null);
|
|
|
|
Assert.NotNull(vm.ErrorMessage);
|
|
Assert.False(vm.AfficherQuestionEnvoiExterne);
|
|
Assert.Equal(0, intervention.EnregistrementsEnvoyes);
|
|
Assert.Equal(0, intervention.EnregistrementsBrouillon);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Composeur système qui CONNAÎT le résultat (iOS avec pièces jointes) :
|
|
/// envoyé → enregistrement direct, sans question ; annulé → brouillon.
|
|
/// </summary>
|
|
[Theory]
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public async Task ComposeurSysteme_ResultatConnu_EnregistreSansQuestion(bool envoye)
|
|
{
|
|
var intervention = new FakeInterventionService();
|
|
var launcher = new FakeEmailLauncher { Resultat = EmailExterneResultat.Confirme(envoye) };
|
|
NouveauEmailViewModel vm = Creer(
|
|
intervention,
|
|
launcher,
|
|
new FakeAppRetourService(),
|
|
new FakePermissionService { MessagerieExterneValue = true });
|
|
|
|
await vm.EnvoyerCommand.ExecuteAsync(null);
|
|
|
|
Assert.False(vm.AfficherQuestionEnvoiExterne);
|
|
Assert.Equal(envoye ? 1 : 0, intervention.EnregistrementsEnvoyes);
|
|
Assert.Equal(envoye ? 0 : 1, intervention.EnregistrementsBrouillon);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pièces jointes cochées : leurs chemins LOCAUX (téléchargés au besoin) sont
|
|
/// transmis à la messagerie ; un document introuvable est simplement omis.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Envoyer_Externe_TransmetLesCheminsDesPiecesJointes()
|
|
{
|
|
string chemin = Path.Combine(Path.GetTempPath(), $"omika-pj-{Guid.NewGuid():N}.pdf");
|
|
await File.WriteAllTextAsync(chemin, "pdf");
|
|
|
|
try
|
|
{
|
|
var documents = new FakeDocumentService { CheminLocal = chemin };
|
|
(NouveauEmailViewModel vm, _, FakeEmailLauncher launcher) = CreerExterne(documents: documents);
|
|
|
|
Guid documentGuid = Guid.NewGuid();
|
|
vm.PiecesJointes.Add(new PieceJointeOption(documentGuid.ToString(), "Rapport", estPhoto: false)
|
|
{
|
|
EstSelectionne = true
|
|
});
|
|
|
|
await vm.EnvoyerCommand.ExecuteAsync(null);
|
|
|
|
Assert.NotNull(launcher.DerniereDemande);
|
|
Assert.Equal(new[] { chemin }, launcher.DerniereDemande!.CheminsPiecesJointes);
|
|
}
|
|
finally
|
|
{
|
|
File.Delete(chemin);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sans moyen d'observer le retour (plateforme de test), la question est posée
|
|
/// immédiatement : aucun enregistrement silencieux dans un état incertain.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Envoyer_Externe_SansObservationDuRetour_PoseLaQuestion()
|
|
{
|
|
var intervention = new FakeInterventionService();
|
|
var launcher = new FakeEmailLauncher();
|
|
NouveauEmailViewModel vm = Creer(
|
|
intervention,
|
|
launcher,
|
|
retour: null,
|
|
new FakePermissionService { MessagerieExterneValue = true });
|
|
|
|
await vm.EnvoyerCommand.ExecuteAsync(null);
|
|
|
|
Assert.True(vm.AfficherQuestionEnvoiExterne);
|
|
Assert.Equal(0, intervention.EnregistrementsEnvoyes);
|
|
Assert.Equal(0, intervention.EnregistrementsBrouillon);
|
|
}
|
|
|
|
/// <summary>Édition d'un brouillon existant : « Oui » met CE brouillon à jour en envoyé.</summary>
|
|
[Fact]
|
|
public async Task Edition_Externe_RepondreOui_MarqueLeBrouillonEnvoye()
|
|
{
|
|
var intervention = new FakeInterventionService();
|
|
var launcher = new FakeEmailLauncher();
|
|
NouveauEmailViewModel vm = Creer(
|
|
intervention,
|
|
launcher,
|
|
new FakeAppRetourService(),
|
|
new FakePermissionService { MessagerieExterneValue = true });
|
|
|
|
var brouillon = new EmailInterventionDto
|
|
{
|
|
Id = "email-42",
|
|
Objet = "Objet",
|
|
Message = "Corps",
|
|
Brouillon = true
|
|
};
|
|
await vm.LoadEditionCommand.ExecuteAsync(
|
|
new EmailEditionNavigation(vm.InterventionGuid, brouillon));
|
|
|
|
vm.Sujet = "Objet modifié";
|
|
vm.Corps = "Corps modifié";
|
|
vm.Destinataires = "client@exemple.fr";
|
|
|
|
await vm.EnvoyerCommand.ExecuteAsync(null);
|
|
await vm.RepondreEnvoiExterneOuiCommand.ExecuteAsync(null);
|
|
|
|
Assert.Equal(1, intervention.EnregistrementsEnvoyes);
|
|
Assert.Equal(0, intervention.EnregistrementsBrouillon);
|
|
Assert.Equal("email-42", intervention.DernierEmailId);
|
|
}
|
|
|
|
// =====================================================================
|
|
// Fakes
|
|
// =====================================================================
|
|
|
|
private sealed class FakeEmailLauncher : IEmailLauncher
|
|
{
|
|
public int Appels { get; private set; }
|
|
|
|
public EmailExterneDemande? DerniereDemande { get; private set; }
|
|
|
|
public EmailExterneResultat Resultat { get; set; } = EmailExterneResultat.LanceSansConfirmation;
|
|
|
|
public Task<bool> ComposeAsync(string email) => Task.FromResult(true);
|
|
|
|
public Task<EmailExterneResultat> ComposerEmailExterneAsync(EmailExterneDemande demande)
|
|
{
|
|
Appels++;
|
|
DerniereDemande = demande;
|
|
return Task.FromResult(Resultat);
|
|
}
|
|
}
|
|
|
|
/// <summary>Retour au premier plan simulé : le retour est immédiat.</summary>
|
|
private sealed class FakeAppRetourService : IAppRetourService
|
|
{
|
|
public IAppRetourAttente Armer() => new RetourImmediat();
|
|
|
|
private sealed class RetourImmediat : IAppRetourAttente
|
|
{
|
|
public Task AttendreAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
private sealed class FakePermissionService : IPermissionService
|
|
{
|
|
public bool MessagerieExterneValue { get; init; }
|
|
|
|
public IReadOnlyDictionary<string, bool> Rights => new Dictionary<string, bool>();
|
|
public void SetRights(IReadOnlyDictionary<string, bool> rights) { }
|
|
public bool HasRight(string key) => true;
|
|
public bool MessagerieExterne => MessagerieExterneValue;
|
|
public void SetMessagerieExterne(bool externe) { }
|
|
public bool PeutRechercherInterventions => true;
|
|
public bool VoirRdvSansRestriction => true;
|
|
public bool PeutVoirAgendaEquipe => true;
|
|
public bool PeutEnvoyerEmail => !MessagerieExterneValue;
|
|
public bool PeutComposerEmail => true;
|
|
public bool PeutCreerRapport => true;
|
|
public bool PeutGererLicence => true;
|
|
public bool PeutSaisirTempsAutrui => true;
|
|
public bool PeutVoirNotesAdmin => true;
|
|
public bool PeutVoirClients => true;
|
|
public bool PeutVoirSites => true;
|
|
public bool PeutVoirDossiers => true;
|
|
}
|
|
|
|
private sealed class FakeInterventionService : IInterventionLocalService
|
|
{
|
|
public int EnvoisInternes { get; private set; }
|
|
|
|
public int EnregistrementsEnvoyes { get; private set; }
|
|
|
|
public int EnregistrementsBrouillon { get; private set; }
|
|
|
|
/// <summary>Identifiant du dernier courriel touché (édition).</summary>
|
|
public string? DernierEmailId { get; private set; }
|
|
|
|
public List<CreateEmailInterventionRequest> DemandesEnvoyees { get; } = [];
|
|
|
|
public Task<EmailInterventionDto> SendEmailAsync(
|
|
Guid interventionGuid,
|
|
string? emailId,
|
|
CreateEmailInterventionRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
EnvoisInternes++;
|
|
return Task.FromResult(new EmailInterventionDto { Id = Guid.NewGuid().ToString() });
|
|
}
|
|
|
|
public Task<EmailInterventionDto> AddEmailAsync(
|
|
Guid interventionGuid,
|
|
CreateEmailInterventionRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
EnregistrementsBrouillon++;
|
|
return Task.FromResult(new EmailInterventionDto { Id = Guid.NewGuid().ToString(), Brouillon = true });
|
|
}
|
|
|
|
public Task<EmailInterventionDto> UpdateEmailAsync(
|
|
Guid interventionGuid,
|
|
string emailId,
|
|
CreateEmailInterventionRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
EnregistrementsBrouillon++;
|
|
return Task.FromResult(new EmailInterventionDto { Id = emailId, Brouillon = true });
|
|
}
|
|
|
|
public Task<EmailInterventionDto> AddEmailEnvoyeExterneAsync(
|
|
Guid interventionGuid,
|
|
CreateEmailInterventionRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
EnregistrementsEnvoyes++;
|
|
DemandesEnvoyees.Add(request);
|
|
return Task.FromResult(new EmailInterventionDto { Id = Guid.NewGuid().ToString() });
|
|
}
|
|
|
|
public Task<EmailInterventionDto> UpdateEmailEnvoyeExterneAsync(
|
|
Guid interventionGuid,
|
|
string emailId,
|
|
CreateEmailInterventionRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
EnregistrementsEnvoyes++;
|
|
DernierEmailId = emailId;
|
|
DemandesEnvoyees.Add(request);
|
|
return Task.FromResult(new EmailInterventionDto { Id = emailId });
|
|
}
|
|
|
|
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 string? CheminLocal { get; init; }
|
|
|
|
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(CheminLocal);
|
|
|
|
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());
|
|
}
|
|
}
|