From f5baae57b76245bfb7520fa4ca8608381b5651b1 Mon Sep 17 00:00:00 2001 From: Sven Hansen <76601644+SyntaxSven@users.noreply.github.com> Date: Tue, 28 Mar 2023 09:56:18 +0200 Subject: [PATCH] Feat/58 unit testing poc (#73) * Release v1.0.0 (#41) * Change formats type to `string` and split with comma separator * Use root configuration instead of configuration section * Fix exceptions not properly displaying in the console * Add all module item types according to documentation * Update application demo GIF * Create code-analysis.yml * Remove comments and enable additional queries * Removed push from code-analysis.yml workflow * Typo code-analysis.yml * Added support for a Word export option * Added footer Epsilon Credits * Created helper class with reformat functions * Reformat code * Improved helper functions, Found that they can be added to records :-) * Reformat files * string interpolation * Feature/cleanup (#40) * Remove obsolete Epsilon.Http.Abstractions project * Fix invalid serializable implementation * Disable unused method return value hint * Change exception to more appropriate one * Update Grade score description * Prevent null grades from exporting * Reduce cognitive complexity to acceptable level * Move logging call * Reduce nesting * Move project name and repository uri to constants * Fix nullability warnings * Remove unused class * Use ?: operator and move constants to top level of class * Reduce loop complexity * Use project name constant in output name export option * Update README.md application demo gif * Add supported formats to README.md Co-authored-by: Jelle Maas * POC Open XML SDK * Update * Add KPI format and structure * Added module names to document * Testing the build and structure of exel files. * Testing release deployment script. * Delete file * POC for excel export * Code reformat * Abstracted format logic from exporters to main logic * Remove typo * Remove unused variable * Headers added * Code cleanup * Merge * Removal nullability warnings * Removal nullability warnings * Removal tasks/awaits because they dont have nay use atm. * Removal tasks/awaits because they dont have nay use atm. * Add exporters return type * Added memoryStream return type for ConsoleModuleExporter * Remove unused ExportOptions from ConsoleModuleExporter * Clean up old merge files * Update continuous-integration.yml * Add tests for HttpService, Exporter and ExportDataPackager * Update continuous-integration.yml * 'Var' is everything * Update continuous-integration.yml * Created unit test for CanvasModuleCollectionFetcher GetAll method * Renamed unit test * Bugfix * Merged feat/42 into feat/58 * Refactored ExcelModuleExporter tests to WordModuleExporter tests --------- Co-authored-by: Neal Geilen Co-authored-by: Jelle Maas Co-authored-by: Sven Hansen <76601644+1SvenHansen@users.noreply.github.com> --- .../CanvasModuleCollectionFetcherTests.cs | 126 ++++++++++++++++++ Epsilon.Tests/ExportDataPackagerTests.cs | 94 +++++++++++++ .../Exporters/WordModuleExporterTests.cs | 55 ++++++++ Epsilon.Tests/Usings.cs | 1 + Epsilon.sln | 2 +- 5 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 Epsilon.Canvas.Tests/CanvasModuleCollectionFetcherTests.cs create mode 100644 Epsilon.Tests/ExportDataPackagerTests.cs create mode 100644 Epsilon.Tests/Exporters/WordModuleExporterTests.cs diff --git a/Epsilon.Canvas.Tests/CanvasModuleCollectionFetcherTests.cs b/Epsilon.Canvas.Tests/CanvasModuleCollectionFetcherTests.cs new file mode 100644 index 00000000..1206d886 --- /dev/null +++ b/Epsilon.Canvas.Tests/CanvasModuleCollectionFetcherTests.cs @@ -0,0 +1,126 @@ +using Epsilon.Abstractions.Model; +using Epsilon.Canvas.Abstractions.Model; +using Epsilon.Canvas.Abstractions.Service; +using Microsoft.Extensions.Logging; +using Moq; +using System.Text.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using Epsilon.Canvas.Abstractions; + +namespace Epsilon.Canvas.Tests +{ + public class CanvasModuleCollectionFetcherTests + { + [Fact] + public async Task GivenAllowedModulesAndCourseId_WhenModuleAndOutcomeServiceAreMocked_ThenReturnsExpectedModuleOutcomeResultCollection() + { + // Arrange + var courseId = 123; + var allowedModules = new[] { "Module 1", "Module 3" }; + var outcomeServiceMock = new Mock(); + var moduleServiceMock = new Mock(); + + var expectedResults = new List + { + new ModuleOutcomeResultCollection( + new Module(1, "Module 1", 3, new List + { + new ModuleItem(1, "Module 1 Item 1", ModuleItemType.Page, 1), + new ModuleItem(2, "Module 1 Item 2", ModuleItemType.Assignment, 2), + new ModuleItem(3, "Module 1 Item 3", ModuleItemType.Quiz, 3) + }), + new OutcomeResultCollection( + new List { }, + new OutcomeResultCollectionLink( + new List + { + new Outcome(1, "Outcome 1", "Outcome 1 EN Short Description NL Long Description"), + new Outcome(2, "Outcome 2", "Outcome 2 EN Short Description NL Long Description") + }, + new List { } + ) + ) + ), + new ModuleOutcomeResultCollection( + new Module(3, "Module 3", 2, new List + { + new ModuleItem(4, "Module 3 Item 1", ModuleItemType.Assignment, 4), + new ModuleItem(5, "Module 3 Item 2", ModuleItemType.Assignment, 5) + }), + new OutcomeResultCollection( + new List { }, + new OutcomeResultCollectionLink( + new List + { + new Outcome(1, "Outcome 1", "Outcome 1 EN Short Description NL Long Description"), + new Outcome(2, "Outcome 2", "Outcome 2 EN Short Description NL Long Description") + }, + new List { } + ) + ) + ) + }; + + outcomeServiceMock + .Setup(s => s.GetResults(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OutcomeResultCollection( + new List + { + new OutcomeResult(false, 3, new OutcomeResultLink("user1", "1", "1", "2")), + new OutcomeResult(true, 4.5, new OutcomeResultLink("user2", "2", "2", "3")), + new OutcomeResult(false, null, new OutcomeResultLink("user1", "1", "1", "3")), + }, + new OutcomeResultCollectionLink( + new List + { + new Outcome(1, "Outcome 1", "Outcome 1 EN Short Description NL Long Description"), + new Outcome(2, "Outcome 2", "Outcome 2 EN Short Description NL Long Description"), + }, + new List + { + new Alignment("1", "Alignment 1", new Uri("https://alignment1.com")), + new Alignment("2", "Alignment 2", new Uri("https://alignment2.com")), + new Alignment("3", "Alignment 3", new Uri("https://alignment3.com")), + } + ) + )); + + moduleServiceMock + .Setup(s => s.GetAll(It.IsAny(), It.IsAny())) + .ReturnsAsync(new List + { + new Module(1, "Module 1", 3, new List + { + new ModuleItem(1, "Module 1 Item 1", ModuleItemType.Page, 1), + new ModuleItem(2, "Module 1 Item 2", ModuleItemType.Assignment, 2), + new ModuleItem(3, "Module 1 Item 3", ModuleItemType.Quiz, 3) + }), + new Module(2, "Module 2", 0, new List()), + new Module(3, "Module 3", 2, new List + { + new ModuleItem(4, "Module 3 Item 1", ModuleItemType.Assignment, 4), + new ModuleItem(5, "Module 3 Item 2", ModuleItemType.Assignment, 5) + }), + }); + + var canvasModuleCollectionFetcher = new CanvasModuleCollectionFetcher( + Mock.Of>(), moduleServiceMock.Object, outcomeServiceMock.Object + ); + + // Act + var result = new List(); + await foreach (var item in canvasModuleCollectionFetcher.GetAll(courseId, allowedModules)) + { + result.Add(item); + } + + // Assert + Assert.Equal(JsonSerializer.Serialize(expectedResults), JsonSerializer.Serialize(result)); + } + } +} diff --git a/Epsilon.Tests/ExportDataPackagerTests.cs b/Epsilon.Tests/ExportDataPackagerTests.cs new file mode 100644 index 00000000..27b86dc6 --- /dev/null +++ b/Epsilon.Tests/ExportDataPackagerTests.cs @@ -0,0 +1,94 @@ +using Epsilon.Abstractions.Model; +using Epsilon.Canvas.Abstractions.Model; +using Epsilon.Export; +using Moq; +using System.Text.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Epsilon.Tests +{ + public class ExportDataPackagerTests + { + [Fact] + public async Task GivenListOfModuleOutcomeResultCollection_WhenRequiringOpenLearningOutcomeStructure_ThenOutcomeStructureShouldBeTransformed() + { + // Arrange + var module = new Module(1, "Module 1", 3, new List + { + new ModuleItem(1, "Module 1 Item 1", ModuleItemType.Page, 1), + new ModuleItem(2, "Module 1 Item 2", ModuleItemType.Assignment, 2), + new ModuleItem(3, "Module 1 Item 3", ModuleItemType.Quiz, 3) + }); + + var outcomes = new List + { + new Outcome(1, "Outcome 1", "Outcome 1 EN Short Description NL Long Description"), + new Outcome(2, "Outcome 2", "Outcome 2 EN Short Description NL Long Description"), + }; + + var alignments = new List + { + new Alignment("1", "Alignment 1", new Uri("https://alignment1.com")), + new Alignment("2", "Alignment 2", new Uri("https://alignment2.com")), + }; + + var outcomeResults = new List + { + new OutcomeResult(false, 3, new OutcomeResultLink("user1", "1", "1", "2")), + new OutcomeResult(true, 4.5, new OutcomeResultLink("user2", "2", "2", "3")), + new OutcomeResult(false, null, new OutcomeResultLink("user1", "1", "1", "3")), + }; + + var links = new OutcomeResultCollectionLink(outcomes, alignments); + + var collection = new OutcomeResultCollection(outcomeResults, links); + + var moduleOutcomeResultCollection = new ModuleOutcomeResultCollection(module, collection); + + var data = new List + { + moduleOutcomeResultCollection + }.ToAsyncEnumerable(); + + var expectedData = new ExportData + { + CourseModules = new List + { + new CourseModule + { + Name = "Module 1", + Outcomes = new List + { + new CourseOutcome + { + Name = "Outcome 1", + Description = "Short Description", + Assignments = new List + { + new CourseAssignment + { + Name = "Alignment 2", + Url = "https://alignment2.com/", + Score = "Satisfactory" + } + } + } + } + } + } + }; + + var exportDataPackager = new ExportDataPackager(); + + // Act + var result = await exportDataPackager.GetExportData(data); + + // Assert + Assert.Equal(JsonSerializer.Serialize(expectedData), JsonSerializer.Serialize(result)); + } + } +} diff --git a/Epsilon.Tests/Exporters/WordModuleExporterTests.cs b/Epsilon.Tests/Exporters/WordModuleExporterTests.cs new file mode 100644 index 00000000..b63843a6 --- /dev/null +++ b/Epsilon.Tests/Exporters/WordModuleExporterTests.cs @@ -0,0 +1,55 @@ +using Epsilon.Abstractions.Model; +using Epsilon.Export; +using Epsilon.Export.Exporters; +using Microsoft.Extensions.Options; +using DocumentFormat.OpenXml.Packaging; + +namespace Epsilon.Tests.Exporters +{ + public class WordModuleExporterTests + { + [Fact] + public async Task GivenExportData_WhenExportToWord_ThenFileShouldContainExportData() + { + // Arrange + var data = new ExportData + { + CourseModules = new List + { + new CourseModule + { + Name = "Module 1", + Outcomes = new List + { + new CourseOutcome + { + Name = "Outcome 1", + Description = "Short Description", + Assignments = new List + { + new CourseAssignment { Name = "Assignment 1", Url = "https://assignment1.com/", Score = "Good" }, + new CourseAssignment { Name = "Assignment 2", Url = "https://assignment2.com/", Score = "Outstanding" }, + } + } + } + } + } + }; + + var moduleExporter = new WordModuleExporter(); + + // Act + using var stream = await moduleExporter.Export(data, "word"); + using var document = WordprocessingDocument.Open(stream, false); + + var content = document?.MainDocumentPart?.Document?.Body?.InnerText; + + // Assert + Assert.Contains("Module 1", content); + Assert.Contains("Outcome 1", content); + Assert.Contains("Short Description", content); + Assert.Contains("Assignment 1", content); + Assert.Contains("Assignment 2", content); + } + } +} diff --git a/Epsilon.Tests/Usings.cs b/Epsilon.Tests/Usings.cs index e69de29b..8c927eb7 100644 --- a/Epsilon.Tests/Usings.cs +++ b/Epsilon.Tests/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/Epsilon.sln b/Epsilon.sln index 655d65c2..0689a3bb 100644 --- a/Epsilon.sln +++ b/Epsilon.sln @@ -57,4 +57,4 @@ Global {194C4770-0BCD-4EC2-BC7C-5D672A3B6557}.Release|Any CPU.ActiveCfg = Release|Any CPU {194C4770-0BCD-4EC2-BC7C-5D672A3B6557}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection -EndGlobal +EndGlobal \ No newline at end of file