Skip to content

Commit

Permalink
#794: Implement C# project setting validators (#882)
Browse files Browse the repository at this point in the history
  • Loading branch information
tombogle authored May 7, 2024
2 parents 350d10d + 44eaa87 commit 227c12b
Show file tree
Hide file tree
Showing 7 changed files with 455 additions and 7 deletions.
113 changes: 112 additions & 1 deletion c-sharp-tests/DummyPapiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
using Paranext.DataProvider.MessageHandlers;
using Paranext.DataProvider.Messages;
using Paranext.DataProvider.MessageTransports;
using Paranext.DataProvider.Projects;

namespace TestParanextDataProvider
{
[ExcludeFromCodeCoverage]
internal class DummyPapiClient : PapiClient
{
private readonly Dictionary<string, Func<MessageEvent, Message?>> _eventHandlers = new();
private readonly Dictionary<string, List<(string newValue, string oldValue)>> _validSettings = new();

public Stack<Message?> EventMessages { get; } = new();

Expand All @@ -30,6 +32,16 @@ public IEnumerable<Message> FakeMessageFromServer(Message message)
return handler.HandleMessage(message);
}

public void AddSettingValueToTreatAsValid(string pbSettingName, string newValue, string oldValue)
{
if (!_validSettings.TryGetValue(pbSettingName, out var values))
{
_validSettings[pbSettingName] = values =
new List<(string newValue, string oldValue)>();
}
values.Add((newValue, oldValue));
}

#region Overrides of PapiClient
public override Task<bool> ConnectAsync()
{
Expand All @@ -50,7 +62,14 @@ public override Task<bool> RegisterRequestHandler(
{
var responder = (MessageHandlerRequestByRequestType)
_messageHandlersByMessageType[MessageType.REQUEST];
responder.SetHandlerForRequestType(requestType, requestHandler);
try
{
responder.SetHandlerForRequestType(requestType, requestHandler);
}
catch (ArgumentException)
{
return Task.FromResult(false);
}

return Task.FromResult(true);
}
Expand All @@ -71,6 +90,98 @@ public override void SendEvent(MessageEvent message)
Message? result = handler(message);
EventMessages.Push(result);
}

public override void SendRequest(string requestType, object requestContents,
Action<bool, object?> responseCallback)
{
Task.Delay(1).ContinueWith(async _ =>
{
bool success = false;
object? result = null;

if ((requestType == "object:ProjectSettingsService.function") &&
(requestContents is string[] details) &&
(details.Length > 2))
{
// If this is a setting known to both Platform.Bible and Paratext, do the
// translation from the Paratext settings key to the PB setting id.
// If it's a custom settings key, then perhaps it's one we've registered to
// handle, and no translation is needed.
var ourSettingName = details[1];
var pbSettingName = ProjectSettings
.GetPlatformBibleSettingNameFromParatextSettingName(ourSettingName);

if (ourSettingName != null)
{
switch (details[0])
{
case "isValid":
if (details.Length == 6 && details[4] == ProjectType.Paratext)
{
success = true;
// Might be a setting we've registered to handle.
var isValidRequestContents = new []
{details[2], details[3], details[5]};
if (TryValidationUsingRegisteredHandler(isValidRequestContents,
ourSettingName, out var isValid))
{
result = isValid;
}
else if (pbSettingName == null)
{
// Per comment in isValid (in
// project-settings.service-host.ts), if there is no
// validator just let the change go through
result = true;
}
else
{
result = _validSettings.TryGetValue(pbSettingName,
out var validValues) && validValues.Any(vv =>
vv.newValue == details[2] &&
vv.oldValue == details[3]);
}
}
break;
case "getDefault":
if (details.Length == 3 &&
details[2] == ProjectType.Paratext &&
pbSettingName != null)
{
success = true;
result = $"default value for {pbSettingName}";
}
break;
default:
break;
}
}
}

await Task.Run(() => responseCallback(success,
JsonSerializer.SerializeToElement(result)));
});
}

private bool TryValidationUsingRegisteredHandler(string[] requestContents, string settingName,
out bool isValid)
{
isValid = true;
if (!_messageHandlersByMessageType.TryGetValue(
MessageType.REQUEST, out var responder))
return false;

var msgRequest = new MessageRequest(ProjectSettingsService.GetValidatorKey(settingName),
GetRequestId(), requestContents);
var responseMsg = responder.HandleMessage(msgRequest).OfType<MessageResponse>()
.FirstOrDefault();

if (responseMsg == null)
return false;

isValid = responseMsg.Success;
return true;
}
#endregion
}
}
142 changes: 142 additions & 0 deletions c-sharp-tests/ProjectSettingsServicesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
using TestParanextDataProvider;

namespace Paranext.DataProvider.Projects.Tests
{
[ExcludeFromCodeCoverage]
public class ProjectSettingsServicesTests
{
[Test]
public void IsValid_ValidLanguage_ReturnsTrue()
{
DummyPapiClient papiClient = new DummyPapiClient();
var newValueJson = JsonConvert.SerializeObject("Spanish");
var currentValueJson = JsonConvert.SerializeObject("German");
papiClient.AddSettingValueToTreatAsValid(ProjectSettings.PB_LANGUAGE,
newValueJson, currentValueJson);
var result = ProjectSettingsService.IsValid(papiClient, newValueJson,
currentValueJson, ProjectSettings.PT_LANGUAGE, "",
ProjectType.Paratext);

Assert.That(result, Is.True);
}

[Test]
public void IsValid_InvalidSetting_ReturnsFalse()
{
DummyPapiClient papiClient = new DummyPapiClient();
var newValueJson = JsonConvert.SerializeObject("Spanish");
var currentValueJson = JsonConvert.SerializeObject("German");
var result = ProjectSettingsService.IsValid(papiClient, newValueJson,
currentValueJson, ProjectSettings.PT_LANGUAGE, "",
ProjectType.Paratext);

Assert.That(result, Is.False);
}

[Test]
public void IsValid_UnexpectedProjectType_ReturnsFalse()
{
DummyPapiClient papiClient = new DummyPapiClient();
var newValueJson = JsonConvert.SerializeObject("Spanish");
var currentValueJson = JsonConvert.SerializeObject("German");
papiClient.AddSettingValueToTreatAsValid(ProjectSettings.PB_LANGUAGE,
newValueJson, currentValueJson);
var result = ProjectSettingsService.IsValid(papiClient, newValueJson,
currentValueJson, ProjectSettings.PT_LANGUAGE, "",
"SomethingElse");

Assert.That(result, Is.False);
}

[Test]
public void GetDefault_KnownProperty_ReturnsDefaultValue()
{
DummyPapiClient papiClient = new DummyPapiClient();
var result = ProjectSettingsService.GetDefault(papiClient, ProjectSettings.PT_LANGUAGE,
ProjectType.Paratext);

Assert.That(result, Is.EqualTo($"default value for {ProjectSettings.PB_LANGUAGE}"));
}

[Test]
public void GetDefault_UnknownProperty_ReturnsNull()
{
DummyPapiClient papiClient = new DummyPapiClient();
var result = ProjectSettingsService.GetDefault(papiClient, "wonky.setting",
ProjectType.Paratext);

Assert.That(result, Is.Null);
}

[Test]
public void RegisterValidator_NewProperty_ReturnsTrue()
{
const string settingKey = "testScripture.MonkeyCount";
(bool result, string? error) MonkeyCountValidator((string newValueJson, string currentValueJson,
string allChangesJson, string projectType) data)
{
try
{
var value = JsonConvert.DeserializeObject(data.newValueJson);
var result = true;
string? error = null;
if (value == null)
{
result = false;
error = "New value must be an integer. It cannot be null";
}
else if ((long)value <= 4)
{
result = false;
error = "Mama called the doctor, and the doctor said, you must have at" +
"least 4 monkeys jumping on the bed!";
}
return (result, error);
}
catch (Exception ex)
{
return (false, ex.Message);
}
}

DummyPapiClient papiClient = new DummyPapiClient();

// Before registering the validator, IsValid should return true.
Assert.That(ProjectSettingsService.IsValid(papiClient, JsonConvert.SerializeObject(2),
JsonConvert.SerializeObject(5), settingKey, "", ProjectType.Paratext), Is.True);

var result = ProjectSettingsService.RegisterValidator(papiClient,
settingKey, MonkeyCountValidator);

Assert.That(result, Is.EqualTo(true));

Assert.That(ProjectSettingsService.IsValid(papiClient, JsonConvert.SerializeObject(1),
JsonConvert.SerializeObject(5), settingKey, "", ProjectType.Paratext), Is.False);
Assert.That(ProjectSettingsService.IsValid(papiClient, JsonConvert.SerializeObject(6),
JsonConvert.SerializeObject(5), settingKey, "", ProjectType.Paratext), Is.True);
}

[Test]
public void RegisterValidator_ExistingProperty_ReturnsFalse()
{
const string settingKey = "testScripture.Oops";
(bool result, string? error) OopsValidator((string newValueJson, string currentValueJson,
string allChangesJson, string projectType) data)
{
return (false, "The Oops property has no valid values. Ha Ha!");
}

DummyPapiClient papiClient = new DummyPapiClient();

var result = ProjectSettingsService.RegisterValidator(papiClient,
settingKey, OopsValidator);

Assert.That(result, Is.EqualTo(true));

Assert.That(ProjectSettingsService.RegisterValidator(papiClient,
settingKey, OopsValidator), Is.EqualTo(false));
}
}
}
49 changes: 45 additions & 4 deletions c-sharp-tests/Projects/ParatextDataProviderTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Nodes;
using Newtonsoft.Json;
using Paranext.DataProvider.MessageHandlers;
using Paranext.DataProvider.Messages;
using Paranext.DataProvider.Projects;
using Paratext.Data;
using Paratext.Data.ProjectSettingsAccess;
using SIL.Scripture;
using static Paranext.DataProvider.Projects.ParatextProjectDataProvider;

namespace TestParanextDataProvider.Projects
{
Expand Down Expand Up @@ -246,14 +249,14 @@ string expectedResult
null,
requestType,
requesterId,
ParatextProjectDataProvider.AllScriptureDataTypes
AllScriptureDataTypes
);

// Verify an update event was sent out properly
Assert.That(updateEvents.Count, Is.EqualTo(1));
Assert.That(
updateEvents[0].Event,
Is.EqualTo(ParatextProjectDataProvider.AllScriptureDataTypes)
Is.EqualTo(AllScriptureDataTypes)
);

// Verify the new text was saved to disk
Expand Down Expand Up @@ -340,14 +343,14 @@ string expectedResult
null,
requestType,
requesterId,
ParatextProjectDataProvider.AllScriptureDataTypes
AllScriptureDataTypes
);

// Verify an update event was sent out properly
Assert.That(updateEvents.Count, Is.EqualTo(1));
Assert.That(
updateEvents[0].Event,
Is.EqualTo(ParatextProjectDataProvider.AllScriptureDataTypes)
Is.EqualTo(AllScriptureDataTypes)
);

// Verify the new text was saved to disk
Expand Down Expand Up @@ -473,5 +476,43 @@ public async Task SetAndGetExtensionData_SavesAndGetsData()

VerifyResponse(result2, null, requestType, requesterId, "Random file contents");
}

/// <summary>
/// Tests that the ParatextProjectDataProvider has successfully registered a validator for
/// the Validity property and that the validator is called to determine that the new value
/// for that property is indeed valid.
/// </summary>
[Test]
public async Task SetProjectSetting_ValidVisibility_Succeeds()
{
DummyParatextProjectDataProvider provider =
new(PdpName, Client, _projectDetails, ParatextProjects);
await provider.RegisterDataProvider();

var result = provider.SetProjectSetting(
JsonConvert.SerializeObject(VisibilitySettingName),
JsonConvert.SerializeObject(ProjectVisibility.Public.ToString()));

Assert.That(result.Success, Is.True);
}

/// <summary>
/// Tests that the ParatextProjectDataProvider has successfully registered a validator for
/// the Validity property and that the validator is called to determine that the new value
/// for that property is indeed invalid.
/// </summary>
[Test]
public async Task SetProjectSetting_InvalidVisibility_DoesNotSucceed()
{
DummyParatextProjectDataProvider provider =
new(PdpName, Client, _projectDetails, ParatextProjects);
await provider.RegisterDataProvider();

var result = provider.SetProjectSetting(
JsonConvert.SerializeObject(VisibilitySettingName),
JsonConvert.SerializeObject(89));

Assert.That(result.Success, Is.False);
}
}
}
Loading

0 comments on commit 227c12b

Please sign in to comment.