From cc5a66eb92c02b62c6ae016146f6c81fffd53b91 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Tue, 10 Dec 2024 14:35:28 -0500 Subject: [PATCH] Do the tests work? --- src/Serval/src/Serval.Client/Client.g.cs | 3 + .../Contracts/WordAlignmentBuildDto.cs | 1 + .../WordAlignmentEnginesController.cs | 3 +- .../WordAlignmentEngineTests.cs | 1726 +++++++++++++++++ 4 files changed, 1732 insertions(+), 1 deletion(-) create mode 100644 src/Serval/test/Serval.ApiServer.IntegrationTests/WordAlignmentEngineTests.cs diff --git a/src/Serval/src/Serval.Client/Client.g.cs b/src/Serval/src/Serval.Client/Client.g.cs index fcd7e48a..0f18973d 100644 --- a/src/Serval/src/Serval.Client/Client.g.cs +++ b/src/Serval/src/Serval.Client/Client.g.cs @@ -10704,6 +10704,9 @@ public partial class WordAlignmentBuild [Newtonsoft.Json.JsonProperty("options", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public object? Options { get; set; } = default!; + [Newtonsoft.Json.JsonProperty("deploymentVersion", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string? DeploymentVersion { get; set; } = default!; + } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] diff --git a/src/Serval/src/Serval.WordAlignment/Contracts/WordAlignmentBuildDto.cs b/src/Serval/src/Serval.WordAlignment/Contracts/WordAlignmentBuildDto.cs index ce109806..9fc55652 100644 --- a/src/Serval/src/Serval.WordAlignment/Contracts/WordAlignmentBuildDto.cs +++ b/src/Serval/src/Serval.WordAlignment/Contracts/WordAlignmentBuildDto.cs @@ -27,4 +27,5 @@ public record WordAlignmentBuildDto /// } /// public object? Options { get; init; } + public string? DeploymentVersion { get; init; } } diff --git a/src/Serval/src/Serval.WordAlignment/Controllers/WordAlignmentEnginesController.cs b/src/Serval/src/Serval.WordAlignment/Controllers/WordAlignmentEnginesController.cs index 191b6d0b..22d6b57e 100644 --- a/src/Serval/src/Serval.WordAlignment/Controllers/WordAlignmentEnginesController.cs +++ b/src/Serval/src/Serval.WordAlignment/Controllers/WordAlignmentEnginesController.cs @@ -868,7 +868,8 @@ private WordAlignmentBuildDto Map(Build source) QueueDepth = source.QueueDepth, State = source.State, DateFinished = source.DateFinished, - Options = source.Options + Options = source.Options, + DeploymentVersion = source.DeploymentVersion }; } diff --git a/src/Serval/test/Serval.ApiServer.IntegrationTests/WordAlignmentEngineTests.cs b/src/Serval/test/Serval.ApiServer.IntegrationTests/WordAlignmentEngineTests.cs new file mode 100644 index 00000000..276608ec --- /dev/null +++ b/src/Serval/test/Serval.ApiServer.IntegrationTests/WordAlignmentEngineTests.cs @@ -0,0 +1,1726 @@ +using Google.Protobuf.WellKnownTypes; +using Serval.WordAlignment.Models; +using Serval.WordAlignment.V1; +using static Serval.ApiServer.Utils; + +namespace Serval.ApiServer; + +#pragma warning disable CS0612 // Type or member is obsolete + +[TestFixture] +[Category("Integration")] +public class WordAlignmentEngineTests +{ + private static readonly WordAlignmentParallelCorpusConfig TestParallelCorpusConfig = + new() + { + Name = "TestCorpus", + SourceCorpusIds = [SOURCE_CORPUS_ID_1], + TargetCorpusIds = [TARGET_CORPUS_ID], + }; + + private static readonly WordAlignmentParallelCorpusConfig TestMixedParallelCorpusConfig = + new() + { + Name = "TestCorpus", + SourceCorpusIds = [SOURCE_CORPUS_ID_1, SOURCE_CORPUS_ID_2], + TargetCorpusIds = [TARGET_CORPUS_ID], + }; + + private static readonly WordAlignmentParallelCorpusConfig TestParallelCorpusConfigScripture = + new() + { + Name = "TestCorpus", + SourceCorpusIds = [SOURCE_CORPUS_ZIP_ID], + TargetCorpusIds = [TARGET_CORPUS_ZIP_ID], + }; + + private const string ECHO_ENGINE1_ID = "e00000000000000000000001"; + private const string ECHO_ENGINE2_ID = "e00000000000000000000002"; + private const string ECHO_ENGINE3_ID = "e00000000000000000000003"; + private const string STATISTICAL_ENGINE_ID = "be0000000000000000000001"; + private const string FILE1_SRC_ID = "f00000000000000000000001"; + private const string FILE1_FILENAME = "file_a"; + private const string FILE2_TRG_ID = "f00000000000000000000002"; + private const string FILE2_FILENAME = "file_b"; + private const string FILE3_SRC_ZIP_ID = "f00000000000000000000003"; + private const string FILE3_FILENAME = "file_c"; + private const string FILE4_TRG_ZIP_ID = "f00000000000000000000004"; + private const string FILE4_FILENAME = "file_d"; + private const string SOURCE_CORPUS_ID_1 = "cc0000000000000000000001"; + private const string SOURCE_CORPUS_ID_2 = "cc0000000000000000000002"; + private const string TARGET_CORPUS_ID = "cc0000000000000000000003"; + private const string SOURCE_CORPUS_ZIP_ID = "cc0000000000000000000004"; + private const string TARGET_CORPUS_ZIP_ID = "cc0000000000000000000005"; + + private const string DOES_NOT_EXIST_ENGINE_ID = "e00000000000000000000004"; + private const string DOES_NOT_EXIST_CORPUS_ID = "c00000000000000000000001"; + + private TestEnvironment _env; + + [SetUp] + public async Task SetUp() + { + _env = new TestEnvironment(); + var e0 = new Engine + { + Id = ECHO_ENGINE1_ID, + Name = "e0", + SourceLanguage = "en", + TargetLanguage = "en", + Type = "EchoWordAlignment", + Owner = "client1", + ParallelCorpora = [] + }; + var e1 = new Engine + { + Id = ECHO_ENGINE2_ID, + Name = "e1", + SourceLanguage = "en", + TargetLanguage = "en", + Type = "EchoWordAlignment", + Owner = "client1", + ParallelCorpora = [] + }; + var e2 = new Engine + { + Id = ECHO_ENGINE3_ID, + Name = "e2", + SourceLanguage = "en", + TargetLanguage = "en", + Type = "EchoWordAlignment", + Owner = "client2", + ParallelCorpora = [] + }; + var se0 = new Engine + { + Id = STATISTICAL_ENGINE_ID, + Name = "se0", + SourceLanguage = "en", + TargetLanguage = "es", + Type = "Statistical", + Owner = "client1", + ParallelCorpora = [] + }; + + await _env.Engines.InsertAllAsync([e0, e1, e2, se0]); + + var srcFile = new DataFiles.Models.DataFile + { + Id = FILE1_SRC_ID, + Owner = "client1", + Name = "src.txt", + Filename = FILE1_FILENAME, + Format = Shared.Contracts.FileFormat.Text + }; + var trgFile = new DataFiles.Models.DataFile + { + Id = FILE2_TRG_ID, + Owner = "client1", + Name = "trg.txt", + Filename = FILE2_FILENAME, + Format = Shared.Contracts.FileFormat.Text + }; + var srcParatextFile = new DataFiles.Models.DataFile + { + Id = FILE3_SRC_ZIP_ID, + Owner = "client1", + Name = "src.zip", + Filename = FILE3_FILENAME, + Format = Shared.Contracts.FileFormat.Paratext + }; + var trgParatextFile = new DataFiles.Models.DataFile + { + Id = FILE4_TRG_ZIP_ID, + Owner = "client1", + Name = "trg.zip", + Filename = FILE4_FILENAME, + Format = Shared.Contracts.FileFormat.Paratext + }; + await _env.DataFiles.InsertAllAsync([srcFile, trgFile, srcParatextFile, trgParatextFile]); + + var srcCorpus = new DataFiles.Models.Corpus + { + Id = SOURCE_CORPUS_ID_1, + Language = "en", + Owner = "client1", + Files = [new() { FileRef = srcFile.Id, TextId = "all" }] + }; + var srcCorpus2 = new DataFiles.Models.Corpus + { + Id = SOURCE_CORPUS_ID_2, + Language = "en", + Owner = "client1", + Files = [new() { FileRef = srcFile.Id, TextId = "all" }] + }; + var trgCorpus = new DataFiles.Models.Corpus + { + Id = TARGET_CORPUS_ID, + Language = "en", + Owner = "client1", + Files = [new() { FileRef = trgFile.Id, TextId = "all" }] + }; + var srcScriptureCorpus = new DataFiles.Models.Corpus + { + Id = SOURCE_CORPUS_ZIP_ID, + Language = "en", + Owner = "client1", + Files = [new() { FileRef = trgParatextFile.Id, TextId = "all" }] + }; + var trgScriptureCorpus = new DataFiles.Models.Corpus + { + Id = TARGET_CORPUS_ZIP_ID, + Language = "en", + Owner = "client1", + Files = [new() { FileRef = srcParatextFile.Id, TextId = "all" }] + }; + + await _env.Corpora.InsertAllAsync([srcCorpus, srcCorpus2, trgCorpus, srcScriptureCorpus, trgScriptureCorpus]); + } + + [Test] + [TestCase(new[] { Scopes.ReadWordAlignmentEngines }, 200)] + [TestCase(new[] { Scopes.ReadFiles }, 403)] //Arbitrary unrelated privilege + public async Task GetAllAsync(IEnumerable scope, int expectedStatusCode) + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(scope); + switch (expectedStatusCode) + { + case 200: + ICollection results = await client.GetAllAsync(); + Assert.That(results, Has.Count.EqualTo(4)); + Assert.That(results.All(eng => eng.SourceLanguage.Equals("en"))); + break; + case 403: + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.GetAllAsync(); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + default: + Assert.Fail("Unanticipated expectedStatusCode. Check test case for typo."); + break; + } + } + + [Test] + [TestCase(new[] { Scopes.ReadWordAlignmentEngines }, 200, ECHO_ENGINE1_ID)] + [TestCase(new[] { Scopes.ReadFiles }, 403, ECHO_ENGINE1_ID)] //Arbitrary unrelated privilege + [TestCase(new[] { Scopes.ReadWordAlignmentEngines }, 403, ECHO_ENGINE3_ID)] //Engine is not owned + [TestCase(new[] { Scopes.ReadWordAlignmentEngines }, 404, DOES_NOT_EXIST_ENGINE_ID)] + [TestCase(new[] { Scopes.ReadWordAlignmentEngines }, 404, "phony_id")] + public async Task GetByIdAsync(IEnumerable scope, int expectedStatusCode, string engineId) + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(scope); + switch (expectedStatusCode) + { + case 200: + WordAlignmentEngine result = await client.GetAsync(engineId); + Assert.That(result.Name, Is.EqualTo("e0")); + break; + case 403: + case 404: + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.GetAsync(engineId); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + default: + Assert.Fail("Unanticipated expectedStatusCode. Check test case for typo."); + break; + } + } + + [Test] + [TestCase(new[] { Scopes.CreateWordAlignmentEngines, Scopes.ReadWordAlignmentEngines }, 201, "EchoWordAlignment")] + [TestCase(new[] { Scopes.CreateWordAlignmentEngines }, 400, "NotARealKindOfMT")] + [TestCase(new[] { Scopes.ReadFiles }, 403, "EchoWordAlignment")] //Arbitrary unrelated privilege + public async Task CreateEngineAsync(IEnumerable scope, int expectedStatusCode, string engineType) + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(scope); + switch (expectedStatusCode) + { + case 201: + WordAlignmentEngine result = await client.CreateAsync( + new WordAlignmentEngineConfig + { + Name = "test", + SourceLanguage = "en", + TargetLanguage = "en", + Type = engineType + } + ); + Assert.That(result.Name, Is.EqualTo("test")); + WordAlignmentEngine? engine = await client.GetAsync(result.Id); + Assert.That(engine, Is.Not.Null); + Assert.That(engine.Name, Is.EqualTo("test")); + break; + case 400: + { + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.CreateAsync( + new WordAlignmentEngineConfig + { + Name = "test", + SourceLanguage = "en", + TargetLanguage = "es", + Type = engineType + } + ); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + } + case 403: + { + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.CreateAsync( + new WordAlignmentEngineConfig + { + Name = "test", + SourceLanguage = "en", + TargetLanguage = "en", + Type = engineType + } + ); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + } + default: + Assert.Fail("Unanticipated expectedStatusCode. Check test case for typo."); + break; + } + } + + [Test] + [TestCase(new[] { Scopes.DeleteWordAlignmentEngines, Scopes.ReadWordAlignmentEngines }, 200, ECHO_ENGINE1_ID)] + [TestCase(new[] { Scopes.ReadWordAlignmentEngines }, 403, ECHO_ENGINE1_ID)] //Arbitrary unrelated privilege + [TestCase(new[] { Scopes.DeleteWordAlignmentEngines }, 404, DOES_NOT_EXIST_ENGINE_ID)] + public async Task DeleteEngineByIdAsync(IEnumerable scope, int expectedStatusCode, string engineId) + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(scope); + switch (expectedStatusCode) + { + case 200: + await client.DeleteAsync(engineId); + ICollection results = await client.GetAllAsync(); + Assert.That(results, Has.Count.EqualTo(3)); + Assert.That(results.All(eng => eng.SourceLanguage.Equals("en"))); + break; + case 403: + case 404: + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.DeleteAsync(engineId); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + default: + Assert.Fail("Unanticipated expectedStatusCode. Check test case for typo."); + break; + } + } + + [Test] + [TestCase(new[] { Scopes.ReadWordAlignmentEngines, Scopes.UpdateWordAlignmentEngines }, 200, ECHO_ENGINE1_ID)] + [TestCase( + new[] { Scopes.ReadWordAlignmentEngines, Scopes.UpdateWordAlignmentEngines }, + 404, + DOES_NOT_EXIST_ENGINE_ID + )] + [TestCase(new[] { Scopes.ReadWordAlignmentEngines, Scopes.UpdateWordAlignmentEngines }, 409, ECHO_ENGINE1_ID)] + [TestCase(new[] { Scopes.ReadFiles }, 403, ECHO_ENGINE1_ID)] //Arbitrary unrelated privilege + public async Task TranslateSegmentWithEngineByIdAsync( + IEnumerable scope, + int expectedStatusCode, + string engineId + ) + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(scope); + switch (expectedStatusCode) + { + case 200: + await _env.Builds.InsertAsync( + new Build { EngineRef = engineId, State = Shared.Contracts.JobState.Completed } + ); + Client.WordAlignmentResult result = await client.GetWordAlignmentAsync( + engineId, + new WordAlignmentRequest { SourceSegment = "This is a test.", TargetSegment = "This is a test." }, + Arg.Any() + ); + Assert.That(result.SourceTokens, Is.EqualTo("This is a test .".Split())); + Assert.That(result.TargetTokens, Is.EqualTo("This is a test .".Split())); + break; + case 409: + { + _env.EchoClient.GetWordAlignmentAsync(Arg.Any()) + .Returns(CreateAsyncUnaryCall(StatusCode.Aborted)); + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.GetWordAlignmentAsync( + engineId, + new WordAlignmentRequest + { + SourceSegment = "This is a test.", + TargetSegment = "This is a test." + } + ); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + } + case 403: + case 404: + { + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.GetWordAlignmentAsync( + engineId, + new WordAlignmentRequest + { + SourceSegment = "This is a test.", + TargetSegment = "This is a test." + } + ); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + } + default: + Assert.Fail("Unanticipated expectedStatusCode. Check test case for typo."); + break; + } + } + + [Test] + public async Task AddParallelCorpusToEngineByIdAsync() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient( + new[] { Scopes.UpdateWordAlignmentEngines } + ); + WordAlignmentParallelCorpus result = await client.AddParallelCorpusAsync( + ECHO_ENGINE1_ID, + TestParallelCorpusConfig + ); + Assert.Multiple(() => + { + Assert.That(result.SourceCorpora.First().Id, Is.EqualTo(SOURCE_CORPUS_ID_1)); + Assert.That(result.TargetCorpora.First().Id, Is.EqualTo(TARGET_CORPUS_ID)); + }); + Engine? engine = await _env.Engines.GetAsync(ECHO_ENGINE1_ID); + if (engine == null) + { + Assert.Fail("Engine not found"); + return; + } + Assert.Multiple(() => + { + Assert.That(engine.ParallelCorpora[0].SourceCorpora[0].Files[0].Filename, Is.EqualTo(FILE1_FILENAME)); + Assert.That(engine.ParallelCorpora[0].TargetCorpora[0].Files[0].Filename, Is.EqualTo(FILE2_FILENAME)); + }); + } + + public void AddParallelCorpusToEngineById_NoSuchEngine() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient( + new[] { Scopes.UpdateWordAlignmentEngines } + ); + + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.AddParallelCorpusAsync(DOES_NOT_EXIST_ENGINE_ID, TestParallelCorpusConfig); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(404)); + } + + [Test] + public void AddParallelCorpusToEngineById_NotAuthorized() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(new[] { Scopes.ReadFiles }); + + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.AddParallelCorpusAsync(ECHO_ENGINE1_ID, TestParallelCorpusConfig); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(403)); + } + + [Test] + public async Task UpdateParallelCorpusByIdForEngineByIdAsync() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + + WordAlignmentParallelCorpus result = await client.AddParallelCorpusAsync( + ECHO_ENGINE1_ID, + TestParallelCorpusConfig + ); + var updateConfig = new WordAlignmentParallelCorpusUpdateConfig + { + SourceCorpusIds = [SOURCE_CORPUS_ID_1], + TargetCorpusIds = [TARGET_CORPUS_ID] + }; + await client.UpdateParallelCorpusAsync(ECHO_ENGINE1_ID, result.Id, updateConfig); + Engine? engine = await _env.Engines.GetAsync(ECHO_ENGINE1_ID); + if (engine == null) + { + Assert.Fail("Engine not found"); + return; + } + Assert.Multiple(() => + { + Assert.That(engine.ParallelCorpora[0].SourceCorpora[0].Files[0].Filename, Is.EqualTo(FILE1_FILENAME)); + Assert.That(engine.ParallelCorpora[0].TargetCorpora[0].Files[0].Filename, Is.EqualTo(FILE2_FILENAME)); + }); + } + + [Test] + public void UpdateParallelCorpusByIdForEngineById_NoSuchCorpus() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + var updateConfig = new WordAlignmentParallelCorpusUpdateConfig + { + SourceCorpusIds = [SOURCE_CORPUS_ID_1], + TargetCorpusIds = [TARGET_CORPUS_ID] + }; + await client.UpdateParallelCorpusAsync(ECHO_ENGINE1_ID, DOES_NOT_EXIST_CORPUS_ID, updateConfig); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(404)); + } + + [Test] + public void UpdateParallelCorpusByIdForEngineById_NoSuchEngine() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + var updateConfig = new WordAlignmentParallelCorpusUpdateConfig + { + SourceCorpusIds = [SOURCE_CORPUS_ID_1], + TargetCorpusIds = [TARGET_CORPUS_ID] + }; + await client.UpdateParallelCorpusAsync(DOES_NOT_EXIST_ENGINE_ID, SOURCE_CORPUS_ID_1, updateConfig); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(404)); + } + + [Test] + public void UpdateParallelCorpusByIdForEngineById_NotAuthorized() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(new[] { Scopes.ReadFiles }); + + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + var updateConfig = new WordAlignmentParallelCorpusUpdateConfig + { + SourceCorpusIds = [SOURCE_CORPUS_ID_1], + TargetCorpusIds = [TARGET_CORPUS_ID] + }; + await client.UpdateParallelCorpusAsync(ECHO_ENGINE1_ID, DOES_NOT_EXIST_CORPUS_ID, updateConfig); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(403)); + } + + [Test] + public async Task GetAllParallelCorporaForEngineByIdAsync() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + + WordAlignmentParallelCorpus result = await client.AddParallelCorpusAsync( + ECHO_ENGINE1_ID, + TestParallelCorpusConfig + ); + WordAlignmentParallelCorpus resultAfterAdd = (await client.GetAllParallelCorporaAsync(ECHO_ENGINE1_ID)).First(); + Assert.Multiple(() => + { + Assert.That(resultAfterAdd.Id, Is.EqualTo(result.Id)); + Assert.That(resultAfterAdd.SourceCorpora.First().Id, Is.EqualTo(result.SourceCorpora.First().Id)); + }); + } + + [Test] + public void GetAllParallelCorporaForEngineById_NoSuchEngine() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + WordAlignmentParallelCorpus result = ( + await client.GetAllParallelCorporaAsync(DOES_NOT_EXIST_ENGINE_ID) + ).First(); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(404)); + } + + [Test] + public void GetAllParallelCorporaForEngineById_NotAuthorized() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(new[] { Scopes.ReadFiles }); + + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + WordAlignmentParallelCorpus result = (await client.GetAllParallelCorporaAsync(ECHO_ENGINE1_ID)).First(); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(403)); + } + + [Test] + public async Task GetParallelCorpusByIdForEngineByIdAsync() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + WordAlignmentParallelCorpus result = await client.AddParallelCorpusAsync( + ECHO_ENGINE1_ID, + TestParallelCorpusConfig + ); + + Assert.That(result, Is.Not.Null); + WordAlignmentParallelCorpus resultAfterAdd = await client.GetParallelCorpusAsync(ECHO_ENGINE1_ID, result.Id); + Assert.Multiple(() => + { + Assert.That(resultAfterAdd.Id, Is.EqualTo(result.Id)); + Assert.That(resultAfterAdd.SourceCorpora[0].Id, Is.EqualTo(result.SourceCorpora[0].Id)); + }); + } + + [Test] + public void GetParallelCorpusByIdForEngineById_NoCorpora() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + WordAlignmentParallelCorpus result_afterAdd = await client.GetParallelCorpusAsync( + ECHO_ENGINE1_ID, + DOES_NOT_EXIST_CORPUS_ID + ); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(404)); + } + + [Test] + public void GetParallelCorpusByIdForEngineById_NoSuchEngine() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + WordAlignmentParallelCorpus result_afterAdd = await client.GetParallelCorpusAsync( + DOES_NOT_EXIST_ENGINE_ID, + SOURCE_CORPUS_ID_1 + ); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(404)); + } + + [Test] + public async Task GetParallelCorpusByIdForEngineById_NoSuchCorpus() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + WordAlignmentParallelCorpus result = await client.AddParallelCorpusAsync( + ECHO_ENGINE1_ID, + TestParallelCorpusConfig + ); + + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + WordAlignmentParallelCorpus result_afterAdd = await client.GetParallelCorpusAsync( + ECHO_ENGINE1_ID, + DOES_NOT_EXIST_CORPUS_ID + ); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(404)); + } + + [Test] + public void GetParallelCorpusByIdForEngineById_NotAuthorized() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(new[] { Scopes.ReadFiles }); + + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + WordAlignmentParallelCorpus result_afterAdd = await client.GetParallelCorpusAsync( + ECHO_ENGINE1_ID, + DOES_NOT_EXIST_CORPUS_ID + ); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(403)); + } + + [Test] + public async Task DeleteParallelCorpusByIdForEngineByIdAsync() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + + WordAlignmentParallelCorpus result = await client.AddParallelCorpusAsync( + ECHO_ENGINE1_ID, + TestParallelCorpusConfig + ); + await client.DeleteParallelCorpusAsync(ECHO_ENGINE1_ID, result.Id); + ICollection resultsAfterDelete = await client.GetAllParallelCorporaAsync( + ECHO_ENGINE1_ID + ); + Assert.That(resultsAfterDelete, Has.Count.EqualTo(0)); + } + + [Test] + public void DeleteParallelCorpusByIdForEngineById_NoSuchCorpus() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.DeleteParallelCorpusAsync(ECHO_ENGINE1_ID, DOES_NOT_EXIST_CORPUS_ID); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(404)); + } + + [Test] + public void DeleteParallelCorpusByIdForEngineById_NoSuchEngine() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.DeleteParallelCorpusAsync(DOES_NOT_EXIST_ENGINE_ID, SOURCE_CORPUS_ID_1); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(404)); + } + + [Test] + public void DeleteParallelCorpusByIdForEngineById_NotAuthorized() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(new[] { Scopes.ReadFiles }); + + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.DeleteParallelCorpusAsync(ECHO_ENGINE1_ID, SOURCE_CORPUS_ID_1); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(403)); + } + + [Test] + public async Task GetAllWordAlignmentsAsync_Exists() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + WordAlignmentParallelCorpus addedCorpus = await client.AddParallelCorpusAsync( + ECHO_ENGINE1_ID, + TestParallelCorpusConfig + ); + + await _env.Engines.UpdateAsync(ECHO_ENGINE1_ID, u => u.Set(e => e.ModelRevision, 1)); + var wordAlignment = new WordAlignment.Models.WordAlignment + { + CorpusRef = addedCorpus.Id, + TextId = "all", + EngineRef = ECHO_ENGINE1_ID, + Refs = ["ref1", "ref2"], + SourceTokens = ["This", "is", "a", "test", "."], + TargetTokens = ["This", "is", "a", "test", "."], + Alignment = CreateNAlignedWordPair(5), + Confidences = [1, 1, 1, 1, 1], + ModelRevision = 1 + }; + await _env.WordAlignments.InsertAsync(wordAlignment); + + ICollection results = await client.GetAllWordAlignmentsAsync( + ECHO_ENGINE1_ID, + addedCorpus.Id + ); + Assert.That(results.All(p => p.TextId == "all"), Is.True); + } + + [Test] + public void GetAllWordAlignmentsAsync_EngineDoesNotExist() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + + ServalApiException? ex = Assert.ThrowsAsync( + () => client.GetAllWordAlignmentsAsync(DOES_NOT_EXIST_ENGINE_ID, "cccccccccccccccccccccccc") + ); + Assert.That(ex?.StatusCode, Is.EqualTo(404)); + } + + [Test] + public void GetAllWordAlignmentsAsync_CorpusDoesNotExist() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + + ServalApiException? ex = Assert.ThrowsAsync( + () => client.GetAllWordAlignmentsAsync(ECHO_ENGINE1_ID, "cccccccccccccccccccccccc") + ); + Assert.That(ex?.StatusCode, Is.EqualTo(404)); + } + + [Test] + public async Task GetAllWordAlignmentsAsync_EngineNotBuilt() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + WordAlignmentParallelCorpus addedCorpus = await client.AddParallelCorpusAsync( + ECHO_ENGINE1_ID, + TestParallelCorpusConfig + ); + + ServalApiException? ex = Assert.ThrowsAsync( + () => client.GetAllWordAlignmentsAsync(ECHO_ENGINE1_ID, addedCorpus.Id) + ); + Assert.That(ex?.StatusCode, Is.EqualTo(409)); + } + + [Test] + public async Task GetAllWordAlignmentsAsync_TextIdExists() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + WordAlignmentParallelCorpus addedCorpus = await client.AddParallelCorpusAsync( + ECHO_ENGINE1_ID, + TestParallelCorpusConfig + ); + + await _env.Engines.UpdateAsync(ECHO_ENGINE1_ID, u => u.Set(e => e.ModelRevision, 1)); + var wordAlignment = new WordAlignment.Models.WordAlignment + { + CorpusRef = addedCorpus.Id, + TextId = "all", + EngineRef = ECHO_ENGINE1_ID, + Refs = ["ref1", "ref2"], + SourceTokens = ["This", "is", "a", "test", "."], + TargetTokens = ["This", "is", "a", "test", "."], + Alignment = CreateNAlignedWordPair(5), + Confidences = [1, 1, 1, 1, 1], + ModelRevision = 1 + }; + await _env.WordAlignments.InsertAsync(wordAlignment); + + ICollection results = await client.GetAllWordAlignmentsAsync( + ECHO_ENGINE1_ID, + addedCorpus.Id, + "all" + ); + Assert.That(results.All(p => p.TextId == "all"), Is.True); + } + + [Test] + public async Task GetAllWordAlignmentsAsync_TextIdDoesNotExist() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + WordAlignmentParallelCorpus addedCorpus = await client.AddParallelCorpusAsync( + ECHO_ENGINE1_ID, + TestParallelCorpusConfig + ); + + await _env.Engines.UpdateAsync(ECHO_ENGINE1_ID, u => u.Set(e => e.ModelRevision, 1)); + var wordAlignment = new WordAlignment.Models.WordAlignment + { + CorpusRef = addedCorpus.Id, + TextId = "all", + EngineRef = ECHO_ENGINE1_ID, + Refs = ["ref1", "ref2"], + SourceTokens = ["This", "is", "a", "test", "."], + TargetTokens = ["This", "is", "a", "test", "."], + Alignment = CreateNAlignedWordPair(5), + Confidences = [1, 1, 1, 1, 1], + ModelRevision = 1 + }; + await _env.WordAlignments.InsertAsync(wordAlignment); + ICollection results = await client.GetAllWordAlignmentsAsync( + ECHO_ENGINE1_ID, + addedCorpus.Id, + "not_the_right_id" + ); + Assert.That(results, Is.Empty); + } + + [Test] + [TestCase(new[] { Scopes.ReadWordAlignmentEngines }, 200, STATISTICAL_ENGINE_ID)] + [TestCase(new[] { Scopes.ReadWordAlignmentEngines }, 404, DOES_NOT_EXIST_ENGINE_ID, false)] + [TestCase(new[] { Scopes.ReadFiles }, 403, ECHO_ENGINE1_ID)] //Arbitrary unrelated privilege + public async Task GetAllBuildsForEngineByIdAsync( + IEnumerable scope, + int expectedStatusCode, + string engineId, + bool addBuild = true + ) + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(scope); + Build? build = null; + if (addBuild) + { + build = new Build { EngineRef = engineId }; + await _env.Builds.InsertAsync(build); + } + switch (expectedStatusCode) + { + case 200: + ICollection results = await client.GetAllBuildsAsync(engineId); + Assert.That(results, Is.Not.Empty); + Assert.Multiple(() => + { + Assert.That(results.First().Revision, Is.EqualTo(1)); + Assert.That(results.First().Id, Is.EqualTo(build?.Id)); + Assert.That(results.First().State, Is.EqualTo(JobState.Pending)); + }); + break; + case 403: + case 404: + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.GetAllBuildsAsync(engineId); + }); + break; + default: + Assert.Fail("Unanticipated expectedStatusCode. Check test case for typo."); + break; + } + } + + [Test] + [TestCase(new[] { Scopes.ReadWordAlignmentEngines }, 200, STATISTICAL_ENGINE_ID)] + [TestCase(new[] { Scopes.ReadWordAlignmentEngines }, 408, STATISTICAL_ENGINE_ID, true)] + [TestCase(new[] { Scopes.ReadWordAlignmentEngines }, 404, DOES_NOT_EXIST_ENGINE_ID, false)] + [TestCase(new[] { Scopes.ReadWordAlignmentEngines }, 404, STATISTICAL_ENGINE_ID, false)] + [TestCase(new[] { Scopes.ReadFiles }, 403, ECHO_ENGINE1_ID)] //Arbitrary unrelated privilege + public async Task GetBuildByIdForEngineByIdAsync( + IEnumerable scope, + int expectedStatusCode, + string engineId, + bool addBuild = true + ) + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(scope); + Build? build = null; + if (addBuild) + { + build = new Build { EngineRef = engineId }; + await _env.Builds.InsertAsync(build); + } + + switch (expectedStatusCode) + { + case 200: + { + Assert.That(build, Is.Not.Null); + WordAlignmentBuild result = await client.GetBuildAsync(engineId, build.Id); + Assert.Multiple(() => + { + Assert.That(result.Revision, Is.EqualTo(1)); + Assert.That(result.Id, Is.EqualTo(build.Id)); + Assert.That(result.State, Is.EqualTo(JobState.Pending)); + }); + break; + } + case 403: + case 404: + { + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.GetBuildAsync(engineId, "bbbbbbbbbbbbbbbbbbbbbbbb"); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + } + case 408: + { + Assert.That(build, Is.Not.Null); + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.GetBuildAsync(engineId, build.Id, 3); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + } + default: + Assert.Fail("Unanticipated expectedStatusCode. Check test case for typo."); + break; + } + } + + [Test] + [TestCase( + new[] { Scopes.UpdateWordAlignmentEngines, Scopes.CreateWordAlignmentEngines, Scopes.ReadWordAlignmentEngines }, + 201, + ECHO_ENGINE1_ID + )] + [TestCase( + new[] { Scopes.UpdateWordAlignmentEngines, Scopes.CreateWordAlignmentEngines, Scopes.ReadWordAlignmentEngines }, + 404, + DOES_NOT_EXIST_ENGINE_ID + )] + [TestCase( + new[] { Scopes.UpdateWordAlignmentEngines, Scopes.CreateWordAlignmentEngines, Scopes.ReadWordAlignmentEngines }, + 400, + ECHO_ENGINE1_ID + )] + [TestCase(new[] { Scopes.ReadFiles }, 403, ECHO_ENGINE1_ID)] //Arbitrary unrelated privilege + public async Task StartBuildForEngineByIdAsync(IEnumerable scope, int expectedStatusCode, string engineId) + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(scope); + TrainingCorpusConfig2 tcc; + WordAlignmentBuildConfig tbc; + switch (expectedStatusCode) + { + case 201: + WordAlignmentParallelCorpus addedCorpus = await client.AddParallelCorpusAsync( + engineId, + TestParallelCorpusConfig + ); + tcc = new TrainingCorpusConfig2 + { + ParallelCorpusId = addedCorpus.Id, + SourceFilters = [new ParallelCorpusFilterConfig2 { TextIds = ["all"] }], + TargetFilters = [new ParallelCorpusFilterConfig2 { TextIds = ["all"] }] + }; + tbc = new WordAlignmentBuildConfig + { + WordAlignOn = [tcc], + TrainOn = [tcc], + Options = """ + {"max_steps":10, + "use_key_terms":false, + "some_double":10.5, + "some_nested": {"more_nested": {"other_double":10.5}}, + "some_string":"string"} + """ + }; + WordAlignmentBuild resultAfterStart; + Assert.ThrowsAsync(async () => + { + resultAfterStart = await client.GetCurrentBuildAsync(engineId); + }); + + WordAlignmentBuild build = await client.StartBuildAsync(engineId, tbc); + Assert.That(build, Is.Not.Null); + + build = await client.GetCurrentBuildAsync(engineId); + Assert.That(build, Is.Not.Null); + + Assert.That(build.DeploymentVersion, Is.Not.Null); + + break; + case 400: + case 403: + case 404: + + tcc = new TrainingCorpusConfig2 + { + ParallelCorpusId = "cccccccccccccccccccccccc", + SourceFilters = [new ParallelCorpusFilterConfig2 { TextIds = ["all"] }], + TargetFilters = [new ParallelCorpusFilterConfig2 { TextIds = ["all"] }] + }; + tbc = new WordAlignmentBuildConfig { WordAlignOn = [tcc], TrainOn = [tcc] }; + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.StartBuildAsync(engineId, tbc); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + default: + Assert.Fail("Unanticipated expectedStatusCode. Check test case for typo."); + break; + } + } + + [TestCase] + public async Task StartBuildForEngineAsync_UnparsableOptions() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + WordAlignmentParallelCorpus addedCorpus = await client.AddParallelCorpusAsync( + ECHO_ENGINE1_ID, + TestParallelCorpusConfig + ); + TrainingCorpusConfig2 tcc = + new() + { + ParallelCorpusId = addedCorpus.Id, + SourceFilters = [new ParallelCorpusFilterConfig2 { TextIds = ["all"] }], + TargetFilters = [new ParallelCorpusFilterConfig2 { TextIds = ["all"] }] + }; + WordAlignmentBuildConfig tbc = + new() + { + WordAlignOn = [tcc], + TrainOn = [tcc], + Options = "unparsable json" + }; + WordAlignmentBuild resultAfterStart; + Assert.ThrowsAsync(async () => + { + resultAfterStart = await client.GetCurrentBuildAsync(ECHO_ENGINE1_ID); + }); + + Assert.That( + () => client.StartBuildAsync(ECHO_ENGINE1_ID, tbc), + Throws.TypeOf().With.Message.Contains("Unable to parse field 'options'") + ); + } + + [Test] + [TestCase(new[] { Scopes.ReadWordAlignmentEngines }, 200, ECHO_ENGINE1_ID)] + [TestCase(new[] { Scopes.ReadWordAlignmentEngines }, 408, ECHO_ENGINE1_ID)] + [TestCase(new[] { Scopes.ReadWordAlignmentEngines }, 204, ECHO_ENGINE1_ID, false)] + [TestCase(new[] { Scopes.ReadWordAlignmentEngines }, 404, DOES_NOT_EXIST_ENGINE_ID, false)] + [TestCase(new[] { Scopes.ReadFiles }, 403, ECHO_ENGINE1_ID, false)] //Arbitrary unrelated privilege + public async Task GetCurrentBuildForEngineByIdAsync( + IEnumerable scope, + int expectedStatusCode, + string engineId, + bool addBuild = true + ) + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(scope); + Build? build = null; + if (addBuild) + { + build = new Build { EngineRef = engineId }; + await _env.Builds.InsertAsync(build); + } + + switch (expectedStatusCode) + { + case 200: + { + Assert.That(build, Is.Not.Null); + WordAlignmentBuild result = await client.GetCurrentBuildAsync(engineId); + Assert.That(result.Id, Is.EqualTo(build.Id)); + break; + } + case 204: + case 403: + case 404: + { + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.GetCurrentBuildAsync(engineId); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + } + case 408: + { + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.GetCurrentBuildAsync(engineId, minRevision: 3); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + } + default: + Assert.Fail("Unanticipated expectedStatusCode. Check test case for typo."); + break; + } + } + + [Test] + [TestCase(new[] { Scopes.UpdateWordAlignmentEngines }, 200, ECHO_ENGINE1_ID)] + [TestCase(new[] { Scopes.UpdateWordAlignmentEngines }, 404, DOES_NOT_EXIST_ENGINE_ID, false)] + [TestCase(new[] { Scopes.UpdateWordAlignmentEngines }, 204, ECHO_ENGINE1_ID, false)] + [TestCase(new[] { Scopes.ReadFiles }, 403, ECHO_ENGINE1_ID, false)] //Arbitrary unrelated privilege + public async Task CancelCurrentBuildForEngineByIdAsync( + IEnumerable scope, + int expectedStatusCode, + string engineId, + bool addBuild = true + ) + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(scope); + if (!addBuild) + { + var build = new Build { EngineRef = engineId }; + await _env.Builds.InsertAsync(build); + _env.StatisticalClient.CancelBuildAsync( + Arg.Any(), + null, + null, + Arg.Any() + ) + .Returns(CreateAsyncUnaryCall(StatusCode.Aborted)); + } + + switch (expectedStatusCode) + { + case 200: + case 204: + await client.CancelBuildAsync(engineId); + break; + case 403: + case 404: + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.CancelBuildAsync(engineId); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + default: + Assert.Fail("Unanticipated expectedStatusCode. Check test case for typo."); + break; + } + } + + [Test] + public async Task StartBuild_ParallelCorpus() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + WordAlignmentParallelCorpus addedCorpus = await client.AddParallelCorpusAsync( + STATISTICAL_ENGINE_ID, + TestParallelCorpusConfig + ); + TrainingCorpusConfig2 tcc = + new() + { + ParallelCorpusId = addedCorpus.Id, + SourceFilters = [new() { CorpusId = SOURCE_CORPUS_ID_1, TextIds = ["all"] }], + TargetFilters = [new() { CorpusId = TARGET_CORPUS_ID, TextIds = ["all"] }] + }; + ; + WordAlignmentBuildConfig tbc = new WordAlignmentBuildConfig + { + WordAlignOn = [tcc], + TrainOn = [tcc], + Options = """ + {"max_steps":10, + "use_key_terms":false, + "some_double":10.5, + "some_nested": {"more_nested": {"other_double":10.5}}, + "some_string":"string"} + """ + }; + WordAlignmentBuild resultAfterStart; + Assert.ThrowsAsync(async () => + { + resultAfterStart = await client.GetCurrentBuildAsync(STATISTICAL_ENGINE_ID); + }); + + WordAlignmentBuild build = await client.StartBuildAsync(STATISTICAL_ENGINE_ID, tbc); + Assert.That(build, Is.Not.Null); + + build = await client.GetCurrentBuildAsync(STATISTICAL_ENGINE_ID); + Assert.That(build, Is.Not.Null); + } + + [Test] + public async Task StartBuildAsync_ParallelCorpus() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + WordAlignmentParallelCorpus addedCorpus = await client.AddParallelCorpusAsync( + STATISTICAL_ENGINE_ID, + TestParallelCorpusConfig + ); + TrainingCorpusConfig2 tcc = + new() + { + ParallelCorpusId = addedCorpus.Id, + SourceFilters = [new() { CorpusId = SOURCE_CORPUS_ID_1, TextIds = ["all"] }], + TargetFilters = [new() { CorpusId = TARGET_CORPUS_ID, TextIds = ["all"] }] + }; + ; + WordAlignmentBuildConfig tbc = new WordAlignmentBuildConfig + { + WordAlignOn = [tcc], + TrainOn = [tcc], + Options = """ + {"max_steps":10, + "use_key_terms":false, + "some_double":10.5, + "some_nested": {"more_nested": {"other_double":10.5}}, + "some_string":"string"} + """ + }; + WordAlignmentBuild resultAfterStart; + Assert.ThrowsAsync(async () => + { + resultAfterStart = await client.GetCurrentBuildAsync(STATISTICAL_ENGINE_ID); + }); + + WordAlignmentBuild build = await client.StartBuildAsync(STATISTICAL_ENGINE_ID, tbc); + Assert.That(build, Is.Not.Null); + + build = await client.GetCurrentBuildAsync(STATISTICAL_ENGINE_ID); + Assert.That(build, Is.Not.Null); + } + + [Test] + public async Task StartBuildAsync_ParallelCorpus_NoFilter() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + WordAlignmentParallelCorpus addedCorpus = await client.AddParallelCorpusAsync( + STATISTICAL_ENGINE_ID, + TestParallelCorpusConfig + ); + TrainingCorpusConfig2 tcc = + new() + { + ParallelCorpusId = addedCorpus.Id, + SourceFilters = [new() { CorpusId = SOURCE_CORPUS_ID_1 }], + TargetFilters = [new() { CorpusId = TARGET_CORPUS_ID }] + }; + ; + WordAlignmentBuildConfig tbc = new WordAlignmentBuildConfig + { + WordAlignOn = [tcc], + TrainOn = [tcc], + Options = """ + {"max_steps":10, + "use_key_terms":false, + "some_double":10.5, + "some_nested": {"more_nested": {"other_double":10.5}}, + "some_string":"string"} + """ + }; + WordAlignmentBuild resultAfterStart; + Assert.ThrowsAsync(async () => + { + resultAfterStart = await client.GetCurrentBuildAsync(STATISTICAL_ENGINE_ID); + }); + + WordAlignmentBuild build = await client.StartBuildAsync(STATISTICAL_ENGINE_ID, tbc); + Assert.That(build, Is.Not.Null); + Assert.That(build.TrainOn, Is.Not.Null); + Assert.That(build.TrainOn.Count, Is.EqualTo(1)); + Assert.That(build.TrainOn[0].SourceFilters, Is.Null); + Assert.That(build.TrainOn[0].TargetFilters, Is.Null); + Assert.That(build.WordAlignOn, Is.Not.Null); + Assert.That(build.WordAlignOn.Count, Is.EqualTo(1)); + Assert.That(build.WordAlignOn[0].SourceFilters, Is.Null); + Assert.That(build.WordAlignOn[0].TargetFilters, Is.Null); + + build = await client.GetCurrentBuildAsync(STATISTICAL_ENGINE_ID); + Assert.That(build, Is.Not.Null); + } + + [Test] + public async Task StartBuildAsync_ParallelCorpus_PretranslateNoCorpusSpecified() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + WordAlignmentParallelCorpus addedParallelCorpus = await client.AddParallelCorpusAsync( + STATISTICAL_ENGINE_ID, + TestMixedParallelCorpusConfig + ); + TrainingCorpusConfig2 wacc = new() { }; + TrainingCorpusConfig2 tcc = new() { ParallelCorpusId = addedParallelCorpus.Id }; + WordAlignmentBuildConfig tbc = new WordAlignmentBuildConfig { WordAlignOn = [wacc], TrainOn = [tcc] }; + WordAlignmentBuild resultAfterStart; + Assert.ThrowsAsync(async () => + { + resultAfterStart = await client.StartBuildAsync(STATISTICAL_ENGINE_ID, tbc); + }); + } + + [Test] + public async Task StartBuildAsync_ParallelCorpus_PretranslateFilterOnMultipleSources() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + WordAlignmentParallelCorpus addedParallelCorpus = await client.AddParallelCorpusAsync( + STATISTICAL_ENGINE_ID, + TestParallelCorpusConfig + ); + TrainingCorpusConfig2 wacc = + new() + { + ParallelCorpusId = addedParallelCorpus.Id, + SourceFilters = + [ + new ParallelCorpusFilterConfig2() { CorpusId = SOURCE_CORPUS_ID_1 }, + new ParallelCorpusFilterConfig2() { CorpusId = SOURCE_CORPUS_ID_2 } + ] + }; + TrainingCorpusConfig2 tcc = new() { ParallelCorpusId = addedParallelCorpus.Id }; + WordAlignmentBuildConfig tbc = new WordAlignmentBuildConfig { WordAlignOn = [wacc], TrainOn = [tcc] }; + Assert.ThrowsAsync(async () => + { + await client.StartBuildAsync(STATISTICAL_ENGINE_ID, tbc); + }); + } + + [Test] + public async Task StartBuildAsync_ParallelCorpus_TrainOnNoCorpusSpecified() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + WordAlignmentParallelCorpus addedParallelCorpus = await client.AddParallelCorpusAsync( + STATISTICAL_ENGINE_ID, + TestParallelCorpusConfig + ); + TrainingCorpusConfig2 wacc = new() { ParallelCorpusId = addedParallelCorpus.Id }; + TrainingCorpusConfig2 tcc = new() { }; + WordAlignmentBuildConfig tbc = new WordAlignmentBuildConfig { WordAlignOn = [wacc], TrainOn = [tcc] }; + WordAlignmentBuild resultAfterStart; + Assert.ThrowsAsync(async () => + { + resultAfterStart = await client.StartBuildAsync(STATISTICAL_ENGINE_ID, tbc); + }); + } + + [Test] + public async Task TryToQueueMultipleBuildsPerSingleUser() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + string engineId = STATISTICAL_ENGINE_ID; + int expectedStatusCode = 409; + WordAlignmentParallelCorpus addedCorpus = await client.AddParallelCorpusAsync( + engineId, + TestParallelCorpusConfig + ); + TrainingCorpusConfig2 wacc = new() { ParallelCorpusId = addedCorpus.Id }; + var tbc = new WordAlignmentBuildConfig { WordAlignOn = [wacc] }; + WordAlignmentBuild build = await client.StartBuildAsync(engineId, tbc); + _env.StatisticalClient.StartBuildAsync(Arg.Any(), null, null, Arg.Any()) + .Returns(CreateAsyncUnaryCall(StatusCode.Aborted)); + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + build = await client.StartBuildAsync(engineId, tbc); + }); + Assert.That(ex, Is.Not.Null); + Assert.That(ex.StatusCode, Is.EqualTo(expectedStatusCode)); + } + + [Test] + public async Task GetWordAlignmentsByTextId() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + WordAlignmentParallelCorpus addedCorpus = await client.AddParallelCorpusAsync( + ECHO_ENGINE1_ID, + TestParallelCorpusConfigScripture + ); + + await _env.Engines.UpdateAsync(ECHO_ENGINE1_ID, u => u.Set(e => e.ModelRevision, 1)); + var wordAlignment = new WordAlignment.Models.WordAlignment + { + CorpusRef = addedCorpus.Id, + TextId = "MAT", + EngineRef = ECHO_ENGINE1_ID, + Refs = ["MAT 1:1"], + SourceTokens = ["This", "is", "a", "test", "."], + TargetTokens = ["This", "is", "a", "test", "."], + Alignment = CreateNAlignedWordPair(5), + Confidences = [1, 1, 1, 1, 1], + ModelRevision = 1 + }; + await _env.WordAlignments.InsertAsync(wordAlignment); + + IList wordAlignments = await client.GetAllWordAlignmentsAsync( + ECHO_ENGINE1_ID, + addedCorpus.Id, + "MAT" + ); + Assert.That(wordAlignments, Has.Count.EqualTo(1)); + Assert.That(wordAlignments[0].SourceTokens, Is.EqualTo(new[] { "This", "is", "a", "test", "." })); + } + + [Test] + public void GetWordAlignmentsByTextId_EngineDoesNotExist() + { + WordAlignmentEnginesClient client = _env.CreateWordAlignmentEnginesClient(); + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.GetAllWordAlignmentsAsync(DOES_NOT_EXIST_ENGINE_ID, DOES_NOT_EXIST_CORPUS_ID, "MAT"); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(404)); + } + + // [Test] + // [TestCase("Nmt")] + // [TestCase("EchoWordAlignment")] + // public async Task GetQueueAsync(string engineType) + // { + // TranslationEngineTypesClient client = _env.CreateTranslationEngineTypesClient(); + // Client.Queue queue = await client.GetQueueAsync(engineType); + // Assert.That(queue.Size, Is.EqualTo(0)); + // } + + // [Test] + // public void GetQueueAsync_NotAuthorized() + // { + // TranslationEngineTypesClient client = _env.CreateTranslationEngineTypesClient([Scopes.ReadFiles]); + // ServalApiException? ex = Assert.ThrowsAsync(async () => + // { + // Client.Queue queue = await client.GetQueueAsync("EchoWordAlignment"); + // }); + // Assert.That(ex, Is.Not.Null); + // Assert.That(ex.StatusCode, Is.EqualTo(403)); + // } + + [Test] + public async Task DataFileUpdate_Propagated() + { + WordAlignmentEnginesClient translationClient = _env.CreateWordAlignmentEnginesClient(); + DataFilesClient dataFilesClient = _env.CreateDataFilesClient(); + CorporaClient corporaClient = _env.CreateCorporaClient(); + await translationClient.AddParallelCorpusAsync(ECHO_ENGINE1_ID, TestParallelCorpusConfig); + + // Get the original files + DataFile orgFileFromClient = await dataFilesClient.GetAsync(FILE1_SRC_ID); + DataFiles.Models.DataFile orgFileFromRepo = (await _env.DataFiles.GetAsync(FILE1_SRC_ID))!; + DataFiles.Models.Corpus orgCorpusFromRepo = (await _env.Corpora.GetAsync(TARGET_CORPUS_ID))!; + Assert.That(orgFileFromClient.Name, Is.EqualTo(orgFileFromRepo.Name)); + Assert.That(orgCorpusFromRepo.Files[0].FileRef, Is.EqualTo(FILE2_TRG_ID)); + + // Update the file + await dataFilesClient.UpdateAsync(FILE1_SRC_ID, new FileParameter(new MemoryStream([1, 2, 3]), "test.txt")); + await corporaClient.UpdateAsync( + TARGET_CORPUS_ID, + [new CorpusFileConfig { FileId = FILE4_TRG_ZIP_ID, TextId = "all" }] + ); + + // Confirm the change is propagated everywhere + DataFiles.Models.DataFile newFileFromRepo = (await _env.DataFiles.GetAsync(FILE1_SRC_ID))!; + Assert.That(newFileFromRepo.Filename, Is.Not.EqualTo(orgFileFromRepo.Filename)); + + Engine newEngine = (await _env.Engines.GetAsync(ECHO_ENGINE1_ID))!; + + // Updated parallel corpus file filename + Assert.That( + newEngine.ParallelCorpora[0].SourceCorpora[0].Files[0].Filename, + Is.EqualTo(newFileFromRepo.Filename) + ); + + // Updated set of new corpus files + Assert.That(newEngine.ParallelCorpora[0].TargetCorpora[0].Id, Is.EqualTo(TARGET_CORPUS_ID)); + Assert.That(newEngine.ParallelCorpora[0].TargetCorpora[0].Files[0].Id, Is.EqualTo(FILE4_TRG_ZIP_ID)); + Assert.That(newEngine.ParallelCorpora[0].TargetCorpora[0].Files[0].Filename, Is.EqualTo(FILE4_FILENAME)); + Assert.That(newEngine.ParallelCorpora[0].TargetCorpora[0].Files.Count, Is.EqualTo(1)); + } + + [TearDown] + public void TearDown() + { + _env.Dispose(); + } + + private static IReadOnlyList CreateNAlignedWordPair(int numberOfAlignedWords) + { + var alignedWordPairs = new List(); + for (int i = 0; i < numberOfAlignedWords; i++) + { + alignedWordPairs.Add(new Shared.Models.AlignedWordPair { SourceIndex = i, TargetIndex = i }); + } + return alignedWordPairs; + } + + private class TestEnvironment : DisposableBase + { + private readonly IServiceScope _scope; + private readonly MongoClient _mongoClient; + + public TestEnvironment() + { + var clientSettings = new MongoClientSettings { LinqProvider = LinqProvider.V2 }; + _mongoClient = new MongoClient(clientSettings); + ResetDatabases(); + + Factory = new ServalWebApplicationFactory(); + _scope = Factory.Services.CreateScope(); + Engines = _scope.ServiceProvider.GetRequiredService>(); + DataFiles = _scope.ServiceProvider.GetRequiredService>(); + Corpora = _scope.ServiceProvider.GetRequiredService>(); + WordAlignments = _scope.ServiceProvider.GetRequiredService< + IRepository + >(); + Builds = _scope.ServiceProvider.GetRequiredService>(); + + EchoClient = Substitute.For(); + EchoClient + .CreateAsync(Arg.Any(), null, null, Arg.Any()) + .Returns(CreateAsyncUnaryCall(new Empty())); + EchoClient + .DeleteAsync(Arg.Any(), null, null, Arg.Any()) + .Returns(CreateAsyncUnaryCall(new Empty())); + EchoClient + .StartBuildAsync(Arg.Any(), null, null, Arg.Any()) + .Returns(CreateAsyncUnaryCall(new Empty())); + EchoClient + .CancelBuildAsync(Arg.Any(), null, null, Arg.Any()) + .Returns(CreateAsyncUnaryCall(new Empty())); + var wordAlignmentResult = new WordAlignment.V1.WordAlignmentResult + { + SourceTokens = { "This is a test .".Split() }, + TargetTokens = { "This is a test .".Split() }, + Confidences = { 1.0, 1.0, 1.0, 1.0, 1.0 }, + Alignment = + { + new WordAlignment.V1.AlignedWordPair { SourceIndex = 0, TargetIndex = 0 }, + new WordAlignment.V1.AlignedWordPair { SourceIndex = 1, TargetIndex = 1 }, + new WordAlignment.V1.AlignedWordPair { SourceIndex = 2, TargetIndex = 2 }, + new WordAlignment.V1.AlignedWordPair { SourceIndex = 3, TargetIndex = 3 }, + new WordAlignment.V1.AlignedWordPair { SourceIndex = 4, TargetIndex = 4 } + } + }; + var wordAlignmentResponse = new GetWordAlignmentResponse { Result = wordAlignmentResult }; + EchoClient + .GetWordAlignmentAsync(Arg.Any(), null, null, Arg.Any()) + .Returns(CreateAsyncUnaryCall(wordAlignmentResponse)); + EchoClient + .GetQueueSizeAsync(Arg.Any(), null, null, Arg.Any()) + .Returns(CreateAsyncUnaryCall(new GetQueueSizeResponse() { Size = 0 })); + + StatisticalClient = Substitute.For(); + StatisticalClient + .CreateAsync(Arg.Any(), null, null, Arg.Any()) + .Returns(CreateAsyncUnaryCall(new Empty())); + StatisticalClient + .DeleteAsync(Arg.Any(), null, null, Arg.Any()) + .Returns(CreateAsyncUnaryCall(new Empty())); + StatisticalClient + .StartBuildAsync(Arg.Any(), null, null, Arg.Any()) + .Returns(CreateAsyncUnaryCall(new Empty())); + StatisticalClient + .CancelBuildAsync(Arg.Any(), null, null, Arg.Any()) + .Returns(CreateAsyncUnaryCall(new Empty())); + StatisticalClient + .GetWordAlignmentAsync(Arg.Any(), null, null, Arg.Any()) + .Returns(CreateAsyncUnaryCall(StatusCode.Unimplemented)); + } + + public ServalWebApplicationFactory Factory { get; } + public IRepository Engines { get; } + public IRepository DataFiles { get; } + public IRepository Corpora { get; } + public IRepository WordAlignments { get; } + public IRepository Builds { get; } + public WordAlignmentEngineApi.WordAlignmentEngineApiClient EchoClient { get; } + public WordAlignmentEngineApi.WordAlignmentEngineApiClient StatisticalClient { get; } + + public WordAlignmentEnginesClient CreateWordAlignmentEnginesClient(IEnumerable? scope = null) + { + scope ??= + [ + Scopes.CreateWordAlignmentEngines, + Scopes.ReadWordAlignmentEngines, + Scopes.UpdateWordAlignmentEngines, + Scopes.DeleteWordAlignmentEngines + ]; + HttpClient httpClient = Factory + .WithWebHostBuilder(builder => + { + builder.ConfigureTestServices(services => + { + GrpcClientFactory grpcClientFactory = Substitute.For(); + grpcClientFactory + .CreateClient("EchoWordAlignment") + .Returns(EchoClient); + grpcClientFactory + .CreateClient("Statistical") + .Returns(StatisticalClient); + services.AddSingleton(grpcClientFactory); + services.AddTransient(CreateFileSystem); + }); + }) + .CreateClient(); + httpClient.DefaultRequestHeaders.Add("Scope", string.Join(" ", scope)); + return new WordAlignmentEnginesClient(httpClient); + } + + public DataFilesClient CreateDataFilesClient() + { + IEnumerable scope = [Scopes.DeleteFiles, Scopes.ReadFiles, Scopes.UpdateFiles, Scopes.CreateFiles]; + HttpClient httpClient = Factory + .WithWebHostBuilder(builder => + { + builder.ConfigureTestServices(services => + { + services.AddTransient(CreateFileSystem); + }); + }) + .CreateClient(); + if (scope is not null) + httpClient.DefaultRequestHeaders.Add("Scope", string.Join(" ", scope)); + return new DataFilesClient(httpClient); + } + + public CorporaClient CreateCorporaClient() + { + IEnumerable scope = [Scopes.DeleteFiles, Scopes.ReadFiles, Scopes.UpdateFiles, Scopes.CreateFiles]; + HttpClient httpClient = Factory.WithWebHostBuilder(_ => { }).CreateClient(); + if (scope is not null) + httpClient.DefaultRequestHeaders.Add("Scope", string.Join(" ", scope)); + return new CorporaClient(httpClient); + } + + public void ResetDatabases() + { + _mongoClient.DropDatabase("serval_test"); + _mongoClient.DropDatabase("serval_test_jobs"); + } + + private static IFileSystem CreateFileSystem(IServiceProvider sp) + { + IFileSystem fileSystem = Substitute.For(); + IOptionsMonitor dataFileOptions = sp.GetRequiredService< + IOptionsMonitor + >(); + fileSystem + .OpenZipFile(GetFilePath(dataFileOptions, FILE3_FILENAME)) + .Returns(ci => + { + IZipContainer source = CreateZipContainer("SRC"); + source.EntryExists("MATSRC.SFM").Returns(true); + string usfm = + $@"\id MAT - SRC +\h Matthew +\c 1 +\p +\v 1 Chapter one, verse one. +\v 2 Chapter one, verse two. +"; + source.OpenEntry("MATSRC.SFM").Returns(ci => new MemoryStream(Encoding.UTF8.GetBytes(usfm))); + return source; + }); + fileSystem + .OpenZipFile(GetFilePath(dataFileOptions, FILE4_FILENAME)) + .Returns(ci => + { + IZipContainer target = CreateZipContainer("TRG"); + target.EntryExists("MATTRG.SFM").Returns(false); + return target; + }); + fileSystem.OpenWrite(Arg.Any()).Returns(ci => new MemoryStream()); + return fileSystem; + } + + private static IZipContainer CreateZipContainer(string name) + { + IZipContainer container = Substitute.For(); + container.EntryExists("Settings.xml").Returns(true); + XElement settingsXml = + new( + "ScriptureText", + new XElement("StyleSheet", "usfm.sty"), + new XElement("Name", name), + new XElement("FullName", name), + new XElement("Encoding", "65001"), + new XElement( + "Naming", + new XAttribute("PrePart", ""), + new XAttribute("PostPart", $"{name}.SFM"), + new XAttribute("BookNameForm", "MAT") + ), + new XElement("BiblicalTermsListSetting", "Major::BiblicalTerms.xml") + ); + container + .OpenEntry("Settings.xml") + .Returns(new MemoryStream(Encoding.UTF8.GetBytes(settingsXml.ToString()))); + container.EntryExists("custom.vrs").Returns(false); + container.EntryExists("usfm.sty").Returns(false); + container.EntryExists("custom.sty").Returns(false); + return container; + } + + private static string GetFilePath(IOptionsMonitor dataFileOptions, string fileName) + { + return Path.Combine(dataFileOptions.CurrentValue.FilesDirectory, fileName); + } + + protected override void DisposeManagedResources() + { + _scope.Dispose(); + Factory.Dispose(); + ResetDatabases(); + } + } +} + +#pragma warning restore CS0612 // Type or member is obsolete