From a21891778033a4c4d4d138a3d4b2b7ec8eda7a6d Mon Sep 17 00:00:00 2001 From: John Lambert Date: Mon, 2 Dec 2024 14:20:26 -0500 Subject: [PATCH] compiles --- .../DataAccessFieldDefinition.cs | 4 - .../src/SIL.DataAccess/MongoRepository.cs | 1 - .../src/SIL.DataAccess/MongoUpdateBuilder.cs | 10 - src/Serval/src/Serval.Client/Client.g.cs | 60 +-- .../Consumers/DataFileUpdatedConsumer.cs | 11 - .../Consumers/GetCorpusConsumer.cs | 28 +- .../Contracts/CorpusFileDto.cs | 2 +- .../Controllers/CorporaController.cs | 16 +- .../Services/CorpusService.cs | 35 +- .../Services/EngineService.cs | 9 +- .../CorporaSyncTests.cs | 362 ++++++++++++++++++ .../TranslationEngineTests.cs | 6 +- .../Services/CorpusServiceTests.cs | 9 +- 13 files changed, 450 insertions(+), 103 deletions(-) delete mode 100644 src/Serval/src/Serval.DataFiles/Consumers/DataFileUpdatedConsumer.cs create mode 100644 src/Serval/test/Serval.ApiServer.IntegrationTests/CorporaSyncTests.cs diff --git a/src/DataAccess/src/SIL.DataAccess/DataAccessFieldDefinition.cs b/src/DataAccess/src/SIL.DataAccess/DataAccessFieldDefinition.cs index aa5d747a..b7be7638 100644 --- a/src/DataAccess/src/SIL.DataAccess/DataAccessFieldDefinition.cs +++ b/src/DataAccess/src/SIL.DataAccess/DataAccessFieldDefinition.cs @@ -17,10 +17,6 @@ LinqProvider linqProvider linqProvider ); string fieldName = rendered.FieldName.Replace(ArrayPosition.All.ToString(CultureInfo.InvariantCulture), "$[]"); - fieldName = fieldName.Replace( - ArrayPosition.NamedArrayFilter.ToString(CultureInfo.InvariantCulture), - "$[arrayFilter]" - ); fieldName = fieldName.Replace(ArrayPosition.FirstMatching.ToString(CultureInfo.InvariantCulture), "$"); if (fieldName != rendered.FieldName) { diff --git a/src/DataAccess/src/SIL.DataAccess/MongoRepository.cs b/src/DataAccess/src/SIL.DataAccess/MongoRepository.cs index 742b2586..8ba08dde 100644 --- a/src/DataAccess/src/SIL.DataAccess/MongoRepository.cs +++ b/src/DataAccess/src/SIL.DataAccess/MongoRepository.cs @@ -154,7 +154,6 @@ await _collection public async Task UpdateAllAsync( Expression> filter, Action> update, - UpdateOptions? options = null, CancellationToken cancellationToken = default ) { diff --git a/src/DataAccess/src/SIL.DataAccess/MongoUpdateBuilder.cs b/src/DataAccess/src/SIL.DataAccess/MongoUpdateBuilder.cs index b98cf02a..e684563f 100644 --- a/src/DataAccess/src/SIL.DataAccess/MongoUpdateBuilder.cs +++ b/src/DataAccess/src/SIL.DataAccess/MongoUpdateBuilder.cs @@ -20,16 +20,6 @@ public IUpdateBuilder Set(Expression> field, TField v return this; } - public IUpdateBuilder Set(Expression> field, Expression filter - where TField : struct - { - if (value.HasValue) - _defs.Add(_updateBuilder.Set(ToFieldDefinition(field), value.Value)); - else - _defs.Add(_updateBuilder.Unset(ToFieldDefinition(field))); - return this; - } - public IUpdateBuilder SetOnInsert(Expression> field, TField value) { _defs.Add(_updateBuilder.SetOnInsert(ToFieldDefinition(field), value)); diff --git a/src/Serval/src/Serval.Client/Client.g.cs b/src/Serval/src/Serval.Client/Client.g.cs index ee4ce398..8421d86f 100644 --- a/src/Serval/src/Serval.Client/Client.g.cs +++ b/src/Serval/src/Serval.Client/Client.g.cs @@ -9291,9 +9291,37 @@ public partial class Corpus [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] public partial class CorpusFile { - [Newtonsoft.Json.JsonProperty("file", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("fileId", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string FileId { get; set; } = default!; + + [Newtonsoft.Json.JsonProperty("textId", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string? TextId { get; set; } = default!; + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class CorpusConfig + { + [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string? Name { get; set; } = default!; + + [Newtonsoft.Json.JsonProperty("language", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Language { get; set; } = default!; + + [Newtonsoft.Json.JsonProperty("files", Required = Newtonsoft.Json.Required.Always)] [System.ComponentModel.DataAnnotations.Required] - public DataFile File { get; set; } = new DataFile(); + public System.Collections.Generic.IList Files { get; set; } = new System.Collections.ObjectModel.Collection(); + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class CorpusFileConfig + { + [Newtonsoft.Json.JsonProperty("fileId", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string FileId { get; set; } = default!; [Newtonsoft.Json.JsonProperty("textId", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string? TextId { get; set; } = default!; @@ -9336,34 +9364,6 @@ public enum FileFormat } - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class CorpusConfig - { - [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string? Name { get; set; } = default!; - - [Newtonsoft.Json.JsonProperty("language", Required = Newtonsoft.Json.Required.Always)] - [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] - public string Language { get; set; } = default!; - - [Newtonsoft.Json.JsonProperty("files", Required = Newtonsoft.Json.Required.Always)] - [System.ComponentModel.DataAnnotations.Required] - public System.Collections.Generic.IList Files { get; set; } = new System.Collections.ObjectModel.Collection(); - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class CorpusFileConfig - { - [Newtonsoft.Json.JsonProperty("fileId", Required = Newtonsoft.Json.Required.Always)] - [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] - public string FileId { get; set; } = default!; - - [Newtonsoft.Json.JsonProperty("textId", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string? TextId { get; set; } = default!; - - } - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] public partial class TranslationEngine { diff --git a/src/Serval/src/Serval.DataFiles/Consumers/DataFileUpdatedConsumer.cs b/src/Serval/src/Serval.DataFiles/Consumers/DataFileUpdatedConsumer.cs deleted file mode 100644 index 0a4cfdf5..00000000 --- a/src/Serval/src/Serval.DataFiles/Consumers/DataFileUpdatedConsumer.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Serval.DataFiles.Consumers; - -public class DataFileUpdatedConsumer(ICorpusService corpusService) : IConsumer -{ - private readonly ICorpusService _corpusService = corpusService; - - public async Task Consume(ConsumeContext context) - { - await _corpusService.UpdateAsync(context.Message.DataFileId, context.CancellationToken); - } -} diff --git a/src/Serval/src/Serval.DataFiles/Consumers/GetCorpusConsumer.cs b/src/Serval/src/Serval.DataFiles/Consumers/GetCorpusConsumer.cs index c369d528..02e7d3c3 100644 --- a/src/Serval/src/Serval.DataFiles/Consumers/GetCorpusConsumer.cs +++ b/src/Serval/src/Serval.DataFiles/Consumers/GetCorpusConsumer.cs @@ -1,8 +1,9 @@ namespace Serval.DataFiles.Consumers; -public class GetCorpusConsumer(ICorpusService corpusService) : IConsumer +public class GetCorpusConsumer(ICorpusService corpusService, IDataFileService dataFileService) : IConsumer { private readonly ICorpusService _corpusService = corpusService; + private readonly IDataFileService _dataFileService = dataFileService; public async Task Consume(ConsumeContext context) { @@ -19,19 +20,13 @@ await context.RespondAsync( CorpusId = corpus.Id, Name = corpus.Name, Language = corpus.Language, - Files = corpus - .Files.Select(f => new CorpusFileResult + Files = await Task.WhenAll( + corpus.Files.Select(async f => new CorpusFileResult { TextId = f.TextId!, - File = new DataFileResult - { - DataFileId = f.File.Id, - Filename = f.File.Filename, - Format = f.File.Format, - Name = f.File.Name - } + File = Map(await _dataFileService.GetAsync(f.FileId)) }) - .ToList() + ) } ); } @@ -42,4 +37,15 @@ await context.RespondAsync( ); } } + + private static DataFileResult Map(DataFile dataFile) + { + return new DataFileResult + { + DataFileId = dataFile.Id, + Name = dataFile.Name, + Filename = dataFile.Filename, + Format = dataFile.Format, + }; + } } diff --git a/src/Serval/src/Serval.DataFiles/Contracts/CorpusFileDto.cs b/src/Serval/src/Serval.DataFiles/Contracts/CorpusFileDto.cs index d2d175be..0efeaac9 100644 --- a/src/Serval/src/Serval.DataFiles/Contracts/CorpusFileDto.cs +++ b/src/Serval/src/Serval.DataFiles/Contracts/CorpusFileDto.cs @@ -2,6 +2,6 @@ namespace Serval.DataFiles.Contracts; public record CorpusFileDto { - public required DataFileDto File { get; init; } + public required string FileId { get; init; } public string? TextId { get; init; } } diff --git a/src/Serval/src/Serval.DataFiles/Controllers/CorporaController.cs b/src/Serval/src/Serval.DataFiles/Controllers/CorporaController.cs index 29bf041e..c30a5ed3 100644 --- a/src/Serval/src/Serval.DataFiles/Controllers/CorporaController.cs +++ b/src/Serval/src/Serval.DataFiles/Controllers/CorporaController.cs @@ -177,7 +177,7 @@ CancellationToken cancellationToken DataFile? dataFile = await _dataFileService.GetAsync(file.FileId, cancellationToken); if (dataFile == null) throw new InvalidOperationException($"DataFile with id {file.FileId} does not exist."); - dataFiles.Add(new CorpusFile { File = dataFile, TextId = file.TextId }); + dataFiles.Add(new CorpusFile { FileId = file.FileId, TextId = file.TextId }); } return dataFiles; } @@ -197,18 +197,6 @@ private CorpusDto Map(Corpus source) private CorpusFileDto Map(CorpusFile source) { - return new CorpusFileDto { File = Map(source.File), TextId = source.TextId }; - } - - private DataFileDto Map(DataFile source) - { - return new DataFileDto - { - Id = source.Id, - Url = _urlService.GetUrl(Endpoints.GetDataFile, new { id = source.Id }), - Name = source.Name, - Format = source.Format, - Revision = source.Revision - }; + return new CorpusFileDto { FileId = source.FileId, TextId = source.TextId }; } } diff --git a/src/Serval/src/Serval.DataFiles/Services/CorpusService.cs b/src/Serval/src/Serval.DataFiles/Services/CorpusService.cs index b1e2dc32..61622a93 100644 --- a/src/Serval/src/Serval.DataFiles/Services/CorpusService.cs +++ b/src/Serval/src/Serval.DataFiles/Services/CorpusService.cs @@ -1,10 +1,14 @@ namespace Serval.DataFiles.Services; -public class CorpusService(IRepository corpora, IDataAccessContext dataAccessContext, IScopedMediator mediator) - : OwnedEntityServiceBase(corpora), - ICorpusService +public class CorpusService( + IRepository corpora, + IDataAccessContext dataAccessContext, + IDataFileService dataFileService, + IScopedMediator mediator +) : OwnedEntityServiceBase(corpora), ICorpusService { private readonly IDataAccessContext _dataAccessContext = dataAccessContext; + private readonly IDataFileService _dataFileService = dataFileService; private readonly IScopedMediator _mediator = mediator; @@ -36,19 +40,13 @@ await _mediator.Publish( new CorpusUpdated { CorpusId = corpus.Id, - Files = corpus - .Files.Select(f => new CorpusFileResult + Files = await Task.WhenAll( + corpus.Files.Select(async f => new CorpusFileResult { TextId = f.TextId!, - File = new DataFileResult - { - DataFileId = f.File.Id, - Filename = f.File.Filename, - Format = f.File.Format, - Name = f.File.Name - } + File = Map(await _dataFileService.GetAsync(f.FileId)) }) - .ToList() + ) }, ct ); @@ -57,4 +55,15 @@ await _mediator.Publish( cancellationToken ); } + + private static DataFileResult Map(DataFile dataFile) + { + return new DataFileResult + { + DataFileId = dataFile.Id, + Name = dataFile.Name, + Filename = dataFile.Filename, + Format = dataFile.Format, + }; + } } diff --git a/src/Serval/src/Serval.Translation/Services/EngineService.cs b/src/Serval/src/Serval.Translation/Services/EngineService.cs index 97c5f63f..5a370397 100644 --- a/src/Serval/src/Serval.Translation/Services/EngineService.cs +++ b/src/Serval/src/Serval.Translation/Services/EngineService.cs @@ -545,7 +545,10 @@ public Task DeleteAllCorpusFilesAsync(string dataFileId, CancellationToken cance ); } - public Task UpdateAllCorporaAsync(DataFileUpdated dataFileUpdated, CancellationToken cancellationToken = default) + public Task UpdateAllCorpusFilesAsync( + DataFileUpdated dataFileUpdated, + CancellationToken cancellationToken = default + ) { return Entities.UpdateAllAsync( e => @@ -555,11 +558,11 @@ public Task UpdateAllCorporaAsync(DataFileUpdated dataFileUpdated, CancellationT ), u => u.Set( - e => e.Corpora[ArrayPosition.All].SourceFiles[ArrayPosition.NamedArrayFilter].Filename, + e => e.Corpora[ArrayPosition.All].SourceFiles[ArrayPosition.All].Filename, dataFileUpdated.Filename ) .Set( - e => e.Corpora[ArrayPosition.All].TargetFiles[ArrayPosition.NamedArrayFilter].Filename, + e => e.Corpora[ArrayPosition.All].TargetFiles[ArrayPosition.All].Filename, dataFileUpdated.Filename ), cancellationToken diff --git a/src/Serval/test/Serval.ApiServer.IntegrationTests/CorporaSyncTests.cs b/src/Serval/test/Serval.ApiServer.IntegrationTests/CorporaSyncTests.cs new file mode 100644 index 00000000..72f2894b --- /dev/null +++ b/src/Serval/test/Serval.ApiServer.IntegrationTests/CorporaSyncTests.cs @@ -0,0 +1,362 @@ +namespace Serval.ApiServer; + +[TestFixture] +[Category("Integration")] +public class CorporaSyncTests +{ + TestEnvironment _env; + + const string ID1 = "000000000000000000000001"; + const string NAME1 = "sample1.txt"; + + const string ID2 = "000000000000000000000002"; + const string NAME2 = "sample2.txt"; + + const string ID3 = "000000000000000000000003"; + const string NAME3 = "sample3.txt"; + const string DOES_NOT_EXIST_ID = "000000000000000000000004"; + + [SetUp] + public async Task SetUp() + { + _env = new TestEnvironment(); + var file1 = new DataFiles.Models.DataFile + { + Id = ID1, + Owner = "client1", + Name = NAME1, + Filename = NAME1, + Format = Shared.Contracts.FileFormat.Text + }; + var file2 = new DataFiles.Models.DataFile + { + Id = ID2, + Owner = "client1", + Name = NAME2, + Filename = NAME2, + Format = Shared.Contracts.FileFormat.Text + }; + var file3 = new DataFiles.Models.DataFile + { + Id = ID3, + Owner = "client2", + Name = NAME3, + Filename = NAME3, + Format = Shared.Contracts.FileFormat.Text + }; + await _env.DataFiles.InsertAllAsync(new[] { file1, file2, file3 }); + } + + [Test] + [TestCase(new[] { Scopes.ReadFiles }, 200)] + // [TestCase(new[] { Scopes.ReadFiles }, 401)] + [TestCase(new[] { Scopes.CreateTranslationEngines }, 403)] + public async Task GetAllAsync(IEnumerable scope, int expectedStatusCode) + { + DataFilesClient client = _env.CreateClient(scope); + switch (expectedStatusCode) + { + case 200: + ICollection results = await client.GetAllAsync(); + + Assert.That(results, Has.Count.EqualTo(2)); + Assert.That(results.All(dataFile => dataFile.Revision == 1)); + break; + case 401: + //TODO setup unauthorized client (verify possibility of 401 - see DFController) + expectedStatusCode = 403; + goto case 403; + case 403: + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.GetAllAsync(); + }); + Assert.That(ex, Is.Not.Null); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + + default: + Assert.Fail("Unanticipated expectedStatusCode. Check test case for typo."); + break; + } + } + + [Test] + [TestCase(new[] { Scopes.ReadFiles }, 200, ID1)] + // [TestCase(new[] { Scopes.ReadFiles }, 401, ID1)] + [TestCase(new[] { Scopes.ReadFiles }, 403, ID3)] + [TestCase(new[] { Scopes.CreateTranslationEngines }, 403, ID1)] + [TestCase(new[] { Scopes.ReadFiles }, 404, DOES_NOT_EXIST_ID)] + [TestCase(new[] { Scopes.ReadFiles }, 404, "phony_id")] + public async Task GetByIDAsync(IEnumerable scope, int expectedStatusCode, string fileId) + { + DataFilesClient client = _env.CreateClient(scope); + switch (expectedStatusCode) + { + case 200: + DataFile result = await client.GetAsync(fileId); + Assert.That(result.Name, Is.EqualTo(NAME1)); + break; + case 401: + //NOTE Covered in end-to-end tests + goto case 403; + case 403: + case 404: + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.GetAsync(fileId); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + default: + Assert.Fail("Invalid expectedStatusCode. Check test case for typo."); + break; + } + } + + [Test] + [TestCase(new[] { Scopes.CreateFiles, Scopes.ReadFiles }, 201)] + // [TestCase(new[] { Scopes.CreateFiles, Scopes.ReadFiles }, 401)] + [TestCase(new[] { Scopes.CreateFiles, Scopes.ReadFiles }, 400)] + [TestCase(new[] { Scopes.ReadFiles }, 403)] + public async Task CreateAsync(IEnumerable scope, int expectedStatusCode) + { + DataFilesClient client = _env.CreateClient(scope); + switch (expectedStatusCode) + { + case 201: + using (var fs = new MemoryStream()) + { + var fp = new FileParameter(fs); + await client.CreateAsync(fp, Client.FileFormat.Text); + ICollection results = await client.GetAllAsync(); + + Assert.That(results, Has.Count.EqualTo(3)); //2 from set-up + 1 added above = 3 + Assert.That(results.All(dataFile => dataFile.Revision == 1)); + } + break; + case 400: + byte[] bytes = new byte[2_000_000_000]; + using (var fs = new MemoryStream(bytes)) + { + var fp = new FileParameter(fs); + fp = new FileParameter(fs); + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.CreateAsync(fp, FileFormat.Text); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + } + break; + case 403: + using (var fs = new MemoryStream()) + { + var fp = new FileParameter(fs); + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.CreateAsync(fp, Client.FileFormat.Text); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + } + break; + default: + Assert.Fail("Unanticipated expectedStatusCode. Check test case for typo."); + break; + } + } + + [Test] + [TestCase(new[] { Scopes.CreateFiles, Scopes.ReadFiles }, 200, "")] + [TestCase(new[] { Scopes.CreateFiles, Scopes.ReadFiles }, 403, ID3)] + [TestCase(new[] { Scopes.CreateFiles, Scopes.UpdateFiles }, 403, "")] + [TestCase(new[] { Scopes.CreateFiles, Scopes.ReadFiles }, 404, DOES_NOT_EXIST_ID)] + public async Task DownloadAsync(IEnumerable scope, int expectedStatusCode, string fileId) + { + DataFilesClient client = _env.CreateClient(scope); + string content = "This is a file."; + byte[] contentBytes = Encoding.UTF8.GetBytes(content); + + DataFile file; + using (var fs = new MemoryStream(contentBytes)) + { + var fp = new FileParameter(fs); + file = await client.CreateAsync(fp, Client.FileFormat.Text); + } + if (fileId == "") + fileId = file.Id; + + switch (expectedStatusCode) + { + case 200: + try + { + FileResponse downloadedFile = await client.DownloadAsync(fileId); + byte[] data; + using (var memoryStream = new MemoryStream()) + { + downloadedFile.Stream.CopyTo(memoryStream); + data = memoryStream.ToArray(); + } + Assert.That(data, Is.EqualTo(contentBytes)); + } + catch (Exception e) + { + Assert.Fail("DownloadAsync threw an exception: " + e.Message); + } + break; + case 400: + { + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.DownloadAsync(fileId); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + } + case 403: + case 404: + { + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.DownloadAsync(fileId); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + } + default: + Assert.Fail("Unanticipated expectedStatusCode. Check test case for typo."); + break; + } + } + + [Test] + [TestCase(new[] { Scopes.UpdateFiles, Scopes.ReadFiles }, 200, ID1)] + [TestCase(new[] { Scopes.UpdateFiles, Scopes.ReadFiles }, 400, ID1)] + [TestCase(new[] { Scopes.UpdateFiles, Scopes.ReadFiles }, 403, ID3)] + [TestCase(new[] { Scopes.ReadFiles }, 403, ID1)] + [TestCase(new[] { Scopes.UpdateFiles, Scopes.ReadFiles }, 404, DOES_NOT_EXIST_ID)] + public async Task UpdateAsync(IEnumerable scope, int expectedStatusCode, string fileId) + { + DataFilesClient client = _env.CreateClient(scope); + switch (expectedStatusCode) + { + case 200: + DataFile result = await client.GetAsync(fileId); + Assert.That(result.Name, Is.EqualTo(NAME1)); + Assert.DoesNotThrowAsync(async () => + { + await client.UpdateAsync(fileId, new FileParameter(new MemoryStream())); + }); + DataFile resultAfterUpdate = await client.GetAsync(fileId); + Assert.That(resultAfterUpdate.Id, Is.EqualTo(ID1)); + break; + case 400: + { + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.UpdateAsync(fileId, new FileParameter(new MemoryStream(new byte[2_000_000_000]))); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + } + case 403: + case 404: + { + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.UpdateAsync(fileId, new FileParameter(new MemoryStream())); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + break; + } + default: + Assert.Fail("Unanticipated expectedStatusCode. Check test case for typo."); + break; + } + } + + [Test] + [TestCase(new[] { Scopes.DeleteFiles, Scopes.ReadFiles }, 200, ID1)] + // [TestCase(new[] { Scopes.ReadFiles }, 401, ID1)] + [TestCase(new[] { Scopes.DeleteFiles, Scopes.ReadFiles }, 403, ID3)] + [TestCase(new[] { Scopes.ReadFiles }, 403, ID1)] + [TestCase(new[] { Scopes.DeleteFiles, Scopes.ReadFiles }, 404, DOES_NOT_EXIST_ID)] + public async Task DeleteAsync(IEnumerable scope, int expectedStatusCode, string fileId) + { + DataFilesClient client = _env.CreateClient(scope); + switch (expectedStatusCode) + { + case 200: + DataFile result = await client.GetAsync(fileId); + Assert.That(result.Name, Is.EqualTo(NAME1)); + await client.DeleteAsync(fileId); + ICollection results = await client.GetAllAsync(); + Assert.That(results, Has.Count.EqualTo(1)); + Assert.That(results.First().Id, Is.EqualTo(ID2)); + break; + case 401: + //NOTE Covered in end-to-end tests + goto case 403; + case 403: + case 404: + ServalApiException? ex = Assert.ThrowsAsync(async () => + { + await client.DeleteAsync(fileId); + }); + Assert.That(ex?.StatusCode, Is.EqualTo(expectedStatusCode)); + ICollection resultsAfterDelete = await client.GetAllAsync(); + Assert.That(resultsAfterDelete, Has.Count.EqualTo(2)); + break; + default: + Assert.Fail("Unanticipated expectedStatusCode. Check test case for typo."); + break; + } + } + + [TearDown] + public void TearDown() + { + _env.Dispose(); + } + + private class TestEnvironment : DisposableBase + { + private readonly MongoClient _mongoClient; + private readonly IServiceScope _scope; + + public TestEnvironment() + { + var clientSettings = new MongoClientSettings { LinqProvider = LinqProvider.V2 }; + _mongoClient = new MongoClient(clientSettings); + ResetDatabases(); + + Factory = new ServalWebApplicationFactory(); + _scope = Factory.Services.CreateScope(); + DataFiles = _scope.ServiceProvider.GetRequiredService>(); + } + + ServalWebApplicationFactory Factory { get; } + public IRepository DataFiles { get; } + + public DataFilesClient CreateClient(IEnumerable scope) + { + HttpClient httpClient = Factory.WithWebHostBuilder(_ => { }).CreateClient(); + if (scope is not null) + httpClient.DefaultRequestHeaders.Add("Scope", string.Join(" ", scope)); + return new DataFilesClient(httpClient); + } + + public void ResetDatabases() + { + _mongoClient.DropDatabase("serval_test"); + _mongoClient.DropDatabase("serval_test_jobs"); + } + + protected override void DisposeManagedResources() + { + _scope.Dispose(); + Factory.Dispose(); + ResetDatabases(); + } + } +} diff --git a/src/Serval/test/Serval.ApiServer.IntegrationTests/TranslationEngineTests.cs b/src/Serval/test/Serval.ApiServer.IntegrationTests/TranslationEngineTests.cs index d66b3557..bb4ee90f 100644 --- a/src/Serval/test/Serval.ApiServer.IntegrationTests/TranslationEngineTests.cs +++ b/src/Serval/test/Serval.ApiServer.IntegrationTests/TranslationEngineTests.cs @@ -184,21 +184,21 @@ public async Task SetUp() Id = SOURCE_CORPUS_ID_1, Language = "en", Owner = "client1", - Files = [new() { File = srcFile, TextId = "all" }] + Files = [new() { FileId = srcFile.Id, TextId = "all" }] }; var srcCorpus2 = new DataFiles.Models.Corpus { Id = SOURCE_CORPUS_ID_2, Language = "en", Owner = "client1", - Files = [new() { File = srcFile, TextId = "all" }] + Files = [new() { FileId = srcFile.Id, TextId = "all" }] }; var trgCorpus = new DataFiles.Models.Corpus { Id = TARGET_CORPUS_ID, Language = "en", Owner = "client1", - Files = [new() { File = trgFile, TextId = "all" }] + Files = [new() { FileId = trgFile.Id, TextId = "all" }] }; await _env.Corpora.InsertAllAsync([srcCorpus, srcCorpus2, trgCorpus]); } diff --git a/src/Serval/test/Serval.DataFiles.Tests/Services/CorpusServiceTests.cs b/src/Serval/test/Serval.DataFiles.Tests/Services/CorpusServiceTests.cs index 22cdd14e..196a69c5 100644 --- a/src/Serval/test/Serval.DataFiles.Tests/Services/CorpusServiceTests.cs +++ b/src/Serval/test/Serval.DataFiles.Tests/Services/CorpusServiceTests.cs @@ -21,7 +21,7 @@ public class CorpusServiceTests Owner = "owner1", Name = "corpus1", Language = "en", - Files = new List() { new() { File = DefaultDataFile } } + Files = new List() { new() { FileId = DefaultDataFile.Id } } }; [Test] @@ -47,7 +47,12 @@ private class TestEnvironment public TestEnvironment() { Corpora = new MemoryRepository(); - Service = new CorpusService(Corpora); + Service = new CorpusService( + Corpora, + Substitute.For(), + Substitute.For(), + Substitute.For() + ); } public MemoryRepository Corpora { get; }