diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/APITest/ChatHostTest/ChatHostTests.cs b/App/backend-api/Microsoft.GS.DPS.Tests/APITest/ChatHostTest/ChatHostTests.cs new file mode 100644 index 00000000..339aaf13 --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/APITest/ChatHostTest/ChatHostTests.cs @@ -0,0 +1,14 @@ +using FluentAssertions; +using Microsoft.GS.DPS.API; +using Microsoft.GS.DPS.Model; +using Moq; + +namespace Microsoft.GS.DPS.Tests.API.Services +{ + public class ChatHostTests + { + + } + +} + diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/Common/MockData.cs b/App/backend-api/Microsoft.GS.DPS.Tests/Common/MockData.cs new file mode 100644 index 00000000..d8798de0 --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/Common/MockData.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.GS.DPS.Tests.Common +{ + public class MockData + { + } +} diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/Images/FileThumbnailServiceTests.cs b/App/backend-api/Microsoft.GS.DPS.Tests/Images/FileThumbnailServiceTests.cs new file mode 100644 index 00000000..1529ec05 --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/Images/FileThumbnailServiceTests.cs @@ -0,0 +1,51 @@ +using Microsoft.GS.DPS.Images; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.GS.DPS.Tests.Images +{ + public class FileThumbnailServiceTests + { + [Theory] + [InlineData("image/jpeg", "IMG")] + [InlineData("image/png", "IMG")] + [InlineData("application/pdf", "PDF")] + [InlineData("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "XLS")] + [InlineData("application/vnd.ms-excel", "XLS")] + [InlineData("application/vnd.openxmlformats-officedocument.presentationml.presentation", "PPT")] + [InlineData("application/vnd.ms-powerpoint", "PPT")] + [InlineData("application/vnd.openxmlformats-officedocument.wordprocessingml.document", "DOC")] + [InlineData("application/msword", "DOC")] + public void GetThumbnail_ShouldGenerateCorrectThumbnail(string contentType, string expectedText) + { + // Act + byte[] thumbnailBytes = FileThumbnailService.GetThumbnail(contentType); + + // Assert + Assert.NotNull(thumbnailBytes); + Assert.True(thumbnailBytes.Length > 0); + + // Additional validation: You can save the byte array as an image and verify the content manually if needed. + // For now, the test ensures that the thumbnail is generated. + } + + [Fact] + public void GetThumbnail_ShouldReturnEmptyThumbnailForUnknownContentType() + { + // Arrange + string contentType = "unknown/type"; + + // Act + byte[] thumbnailBytes = FileThumbnailService.GetThumbnail(contentType); + + // Assert + Assert.NotNull(thumbnailBytes); + Assert.True(thumbnailBytes.Length > 0); + + // Note: Since the logic does not handle unknown types explicitly, it may default to an empty thumbnail. + } + } +} diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/Microsoft.GS.DPS.Tests.csproj b/App/backend-api/Microsoft.GS.DPS.Tests/Microsoft.GS.DPS.Tests.csproj new file mode 100644 index 00000000..b844d159 --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/Microsoft.GS.DPS.Tests.csproj @@ -0,0 +1,33 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/ChatHost/AnswerTests.cs b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/ChatHost/AnswerTests.cs new file mode 100644 index 00000000..283d99e5 --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/ChatHost/AnswerTests.cs @@ -0,0 +1,121 @@ +using Microsoft.GS.DPS.Model.ChatHost; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.GS.DPS.Tests.ModelsTest.ChatHost +{ + + public class AnswerTests + { + [Fact] + public void Test_Response_Property_Setter_Getter() + { + // Arrange + var answer = new Answer(); + string expectedResponse = "This is a response."; + + // Act + answer.Response = expectedResponse; + + // Assert + Assert.Equal(expectedResponse, answer.Response); + } + + [Fact] + public void Test_Followings_Property_Setter_Getter() + { + // Arrange + var answer = new Answer(); + string[] expectedFollowings = new string[] { "Followup1", "Followup2" }; + + // Act + answer.Followings = expectedFollowings; + + // Assert + Assert.Equal(expectedFollowings, answer.Followings); + } + + [Fact] + public void Test_Keywords_Property_Setter_Getter() + { + // Arrange + var answer = new Answer(); + string[] expectedKeywords = new string[] { "Keyword1", "Keyword2" }; + + // Act + answer.Keywords = expectedKeywords; + + // Assert + Assert.Equal(expectedKeywords, answer.Keywords); + } + + [Fact] + public void Test_Followings_Empty_Array() + { + // Arrange + var answer = new Answer(); + + // Act + answer.Followings = new string[] { }; + + // Assert + Assert.Empty(answer.Followings); + } + + [Fact] + public void Test_Keywords_Empty_Array() + { + // Arrange + var answer = new Answer(); + + // Act + answer.Keywords = new string[] { }; + + // Assert + Assert.Empty(answer.Keywords); + } + + [Fact] + public void Test_Null_Followings_Property() + { + // Arrange + var answer = new Answer(); + + // Act + answer.Followings = null; + + // Assert + Assert.Null(answer.Followings); + } + + [Fact] + public void Test_Null_Keywords_Property() + { + // Arrange + var answer = new Answer(); + + // Act + answer.Keywords = null; + + // Assert + Assert.Null(answer.Keywords); + } + + [Fact] + public void Test_Response_IsNullInitially() + { + // Arrange + var answer = new Answer(); + + // Act + string response = answer.Response; + + // Assert + Assert.Null(response); + } + } +} + diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/ChatHost/ChatRequestTest.cs b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/ChatHost/ChatRequestTest.cs new file mode 100644 index 00000000..012d4215 --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/ChatHost/ChatRequestTest.cs @@ -0,0 +1,88 @@ +using FluentValidation.TestHelper; +using Microsoft.GS.DPS.Model.ChatHost; +using Xunit; + +namespace Microsoft.GS.DPS.Tests.ModelsTest.ChatHost +{ + public class ChatRequestTest + { + private readonly ChatRequestValidator _validator; + + public ChatRequestTest() + { + _validator = new ChatRequestValidator(); + } + + [Fact] + public void Should_Have_Error_When_Question_Is_Null() + { + // Arrange + var request = new ChatRequest { Question = null }; + + // Act + var result = _validator.TestValidate(request); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Question) + .WithErrorMessage("'Question' must not be empty."); + } + + [Fact] + public void Should_Have_Error_When_Question_Is_Empty() + { + // Arrange + var request = new ChatRequest { Question = string.Empty }; + + // Act + var result = _validator.TestValidate(request); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Question) + .WithErrorMessage("'Question' must not be empty."); + } + + [Fact] + public void Should_Not_Have_Error_When_Question_Is_Valid() + { + // Arrange + var request = new ChatRequest { Question = "What is your name?" }; + + // Act + var result = _validator.TestValidate(request); + + // Assert + result.ShouldNotHaveValidationErrorFor(x => x.Question); + } + + [Fact] + public void Should_Not_Have_Error_When_ChatSessionId_Is_Valid() + { + // Arrange + var request = new ChatRequest { Question = "What is the time?", ChatSessionId = "session123" }; + + // Act + var result = _validator.TestValidate(request); + + // Assert + result.ShouldNotHaveValidationErrorFor(x => x.ChatSessionId); + } + + [Fact] + public void Should_Not_Have_Error_When_DocumentIds_Is_Valid() + { + // Arrange + var request = new ChatRequest + { + Question = "What is the time?", + DocumentIds = new string[5] // Valid length (less than 10) + }; + + // Act + var result = _validator.TestValidate(request); + + // Assert + result.ShouldNotHaveValidationErrorFor(x => x.DocumentIds); + } + } +} + diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/ChatHost/ChatResponseAsyncTests.cs b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/ChatHost/ChatResponseAsyncTests.cs new file mode 100644 index 00000000..49e15155 --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/ChatHost/ChatResponseAsyncTests.cs @@ -0,0 +1,39 @@ +using Microsoft.GS.DPS.Model.ChatHost; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.GS.DPS.Tests.ModelsTest.ChatHost +{ + public class ChatResponseAsyncTests + { + [Fact] + public void ChatResponseAsync_SetAndGetProperties_ShouldWork() + { + // Arrange + var chatResponseAsync = new ChatResponseAsync + { + ChatSessionId = "asyncSession123", + Answer = "This is an asynchronous answer.", + DocumentIds = new[] { "doc3", "doc4" }, + SuggestingQuestions = new[] { "What is a chatbot?", "What are its uses?" }, + Keywords = new[] { "chatbot", "AI" } + }; + + // Act & Assert + Assert.Equal("asyncSession123", chatResponseAsync.ChatSessionId); + Assert.Equal("This is an asynchronous answer.", chatResponseAsync.Answer); + Assert.Equal(2, chatResponseAsync.DocumentIds.Length); + Assert.Equal("doc3", chatResponseAsync.DocumentIds[0]); + Assert.Equal("doc4", chatResponseAsync.DocumentIds[1]); + Assert.Equal(2, chatResponseAsync.SuggestingQuestions.Length); + Assert.Equal("What is a chatbot?", chatResponseAsync.SuggestingQuestions[0]); + Assert.Equal("What are its uses?", chatResponseAsync.SuggestingQuestions[1]); + Assert.Equal(2, chatResponseAsync.Keywords.Length); + Assert.Equal("chatbot", chatResponseAsync.Keywords[0]); + Assert.Equal("AI", chatResponseAsync.Keywords[1]); + } + } +} diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/ChatHost/ChatResponseTests.cs b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/ChatHost/ChatResponseTests.cs new file mode 100644 index 00000000..fd77e41d --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/ChatHost/ChatResponseTests.cs @@ -0,0 +1,39 @@ +using Microsoft.GS.DPS.Model.ChatHost; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.GS.DPS.Tests.ModelsTest.ChatHost +{ + public class ChatResponseTests + { + [Fact] + public void ChatResponse_SetAndGetProperties_ShouldWork() + { + // Arrange + var chatResponse = new ChatResponse + { + ChatSessionId = "session123", + Answer = "This is a sample answer.", + DocumentIds = new[] { "doc1", "doc2" }, + SuggestingQuestions = new[] { "What is AI?", "How does it work?" }, + Keywords = new[] { "AI", "Technology" } + }; + + // Act & Assert + Assert.Equal("session123", chatResponse.ChatSessionId); + Assert.Equal("This is a sample answer.", chatResponse.Answer); + Assert.Equal(2, chatResponse.DocumentIds.Length); + Assert.Equal("doc1", chatResponse.DocumentIds[0]); + Assert.Equal("doc2", chatResponse.DocumentIds[1]); + Assert.Equal(2, chatResponse.SuggestingQuestions.Length); + Assert.Equal("What is AI?", chatResponse.SuggestingQuestions[0]); + Assert.Equal("How does it work?", chatResponse.SuggestingQuestions[1]); + Assert.Equal(2, chatResponse.Keywords.Length); + Assert.Equal("AI", chatResponse.Keywords[0]); + Assert.Equal("Technology", chatResponse.Keywords[1]); + } + } +} diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/KernalMemory/DocumentDeletedResultTests.cs b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/KernalMemory/DocumentDeletedResultTests.cs new file mode 100644 index 00000000..b8e728ba --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/KernalMemory/DocumentDeletedResultTests.cs @@ -0,0 +1,31 @@ +using Microsoft.GS.DPS.Model.KernelMemory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.GS.DPS.Tests.ModelsTest.KernalMemory +{ + public class DocumentDeletedResultTests + { + [Fact] + public void IsDeleted_ShouldReturnCorrectValue() + { + // Arrange + var documentDeletedResult = new DocumentDeletedResult(); + + // Act + documentDeletedResult.IsDeleted = true; + + // Assert + Assert.True(documentDeletedResult.IsDeleted); + + // Act + documentDeletedResult.IsDeleted = false; + + // Assert + Assert.False(documentDeletedResult.IsDeleted); + } + } +} diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/KernalMemory/DocumentImportedResultTests.cs b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/KernalMemory/DocumentImportedResultTests.cs new file mode 100644 index 00000000..144d59c2 --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/KernalMemory/DocumentImportedResultTests.cs @@ -0,0 +1,107 @@ +using Microsoft.GS.DPS.Model.KernelMemory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.GS.DPS.Tests.ModelsTest.KernalMemory +{ + public class DocumentImportedResultTests + { + [Fact] + public void Constructor_ShouldInitializeProperties() + { + // Arrange + var importedTime = DateTime.UtcNow; + var processingTime = TimeSpan.FromMinutes(2); + var keywords = new Dictionary { { "Author", "John Doe" } }; + + // Act + var documentImportedResult = new DocumentImportedResult + { + DocumentId = "12345", + ImportedTime = importedTime, + FileName = "example.pdf", + ProcessingTime = processingTime, + MimeType = "application/pdf", + Keywords = keywords, + Summary = "This is a summary." + }; + + // Assert + Assert.Equal("12345", documentImportedResult.DocumentId); + Assert.Equal(importedTime, documentImportedResult.ImportedTime); + Assert.Equal("example.pdf", documentImportedResult.FileName); + Assert.Equal(processingTime, documentImportedResult.ProcessingTime); + Assert.Equal("application/pdf", documentImportedResult.MimeType); + Assert.Equal(keywords, documentImportedResult.Keywords); + Assert.Equal("This is a summary.", documentImportedResult.Summary); + } + + [Fact] + public void Keywords_ShouldHandleNullAndNonNullValues() + { + // Arrange + var documentImportedResult = new DocumentImportedResult(); + + // Act & Assert for null + documentImportedResult.Keywords = null; + Assert.Null(documentImportedResult.Keywords); + + // Act & Assert for non-null + var keywords = new Dictionary { { "Key1", "Value1" } }; + documentImportedResult.Keywords = keywords; + + Assert.NotNull(documentImportedResult.Keywords); + Assert.Single(documentImportedResult.Keywords); + Assert.Equal("Value1", documentImportedResult.Keywords["Key1"]); + } + + [Fact] + public void ProcessingTime_ShouldAcceptValidTimeSpans() + { + // Arrange + var documentImportedResult = new DocumentImportedResult(); + var processingTime = TimeSpan.FromHours(1); + + // Act + documentImportedResult.ProcessingTime = processingTime; + + // Assert + Assert.Equal(processingTime, documentImportedResult.ProcessingTime); + } + + [Fact] + public void Summary_ShouldAcceptValidStrings() + { + // Arrange + var documentImportedResult = new DocumentImportedResult(); + var summary = "Test summary."; + + // Act + documentImportedResult.Summary = summary; + + // Assert + Assert.Equal(summary, documentImportedResult.Summary); + } + + [Fact] + public void DefaultValues_ShouldBeInitializedCorrectly() + { + // Arrange & Act + var documentImportedResult = new DocumentImportedResult(); + + // Assert default values + Assert.Null(documentImportedResult.DocumentId); + Assert.Equal(default, documentImportedResult.ImportedTime); + Assert.Null(documentImportedResult.FileName); + Assert.Equal(default, documentImportedResult.ProcessingTime); + Assert.Null(documentImportedResult.MimeType); + Assert.Null(documentImportedResult.Keywords); + Assert.Null(documentImportedResult.Summary); + } + } + + +} diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/KernalMemory/SearchParameterTests.cs b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/KernalMemory/SearchParameterTests.cs new file mode 100644 index 00000000..92973e1d --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/KernalMemory/SearchParameterTests.cs @@ -0,0 +1,90 @@ +using Microsoft.GS.DPS.Model.KernelMemory; +using Microsoft.KernelMemory.Context; +using Microsoft.KernelMemory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.GS.DPS.Tests.ModelsTest.KernalMemory +{ + + public class SearchParameterTests + { + [Fact] + public void Constructor_ShouldInitializeDefaultValues() + { + // Arrange & Act + var searchParameter = new SearchParameter(); + + // Assert + Assert.Equal(string.Empty, searchParameter.query); + Assert.Null(searchParameter.MemoryFilter); + Assert.Null(searchParameter.MemoryFilters); + Assert.Equal(0.0, searchParameter.minRelevance); + Assert.Equal(-1, searchParameter.limit); + Assert.Null(searchParameter.Context); + } + + [Fact] + public void Properties_ShouldSetAndGetValues() + { + // Arrange + var searchParameter = new SearchParameter(); + var memoryFilter = new MemoryFilter(); + var memoryFilters = new List { memoryFilter }; + var contextMock = new Moq.Mock(); + + // Act + searchParameter.query = "test query"; + searchParameter.MemoryFilter = memoryFilter; + searchParameter.MemoryFilters = memoryFilters; + searchParameter.minRelevance = 0.75; + searchParameter.limit = 10; + searchParameter.Context = contextMock.Object; + + // Assert + Assert.Equal("test query", searchParameter.query); + Assert.Equal(memoryFilter, searchParameter.MemoryFilter); + Assert.Equal(memoryFilters, searchParameter.MemoryFilters); + Assert.Equal(0.75, searchParameter.minRelevance); + Assert.Equal(10, searchParameter.limit); + Assert.Equal(contextMock.Object, searchParameter.Context); + } + + [Fact] + public void MemoryFilters_ShouldHandleNullAndNonNullValues() + { + // Arrange + var searchParameter = new SearchParameter(); + + // Act + searchParameter.MemoryFilters = null; + + // Assert + Assert.Null(searchParameter.MemoryFilters); + + // Act + var memoryFilters = new List { new MemoryFilter() }; + searchParameter.MemoryFilters = memoryFilters; + + // Assert + Assert.NotNull(searchParameter.MemoryFilters); + Assert.Single(searchParameter.MemoryFilters); + } + + [Fact] + public void Limit_ShouldAcceptNegativeValuesAsDefaults() + { + // Arrange + var searchParameter = new SearchParameter(); + + // Act + searchParameter.limit = -1; + + // Assert + Assert.Equal(-1, searchParameter.limit); + } + } +} diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/UserInterface/DocumentQuerySetTests.cs b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/UserInterface/DocumentQuerySetTests.cs new file mode 100644 index 00000000..491983ef --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/UserInterface/DocumentQuerySetTests.cs @@ -0,0 +1,148 @@ +using Xunit; +using Microsoft.GS.DPS.Model.UserInterface; +using Microsoft.GS.DPS.Storage.Document.Entities; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.GS.DPS.Tests.ModelsTest.UserInterface +{ + public class DocumentQuerySetTests + { + [Fact] + public void DocumentQuerySet_Should_Have_Documents_Collection() + { + // Arrange + var documents = new List + { + new Document { /* initialize with some properties */ }, + new Document { /* initialize with some properties */ } + }; + + var querySet = new DocumentQuerySet + { + documents = documents, + keywordFilterInfo = new Dictionary>(), + TotalPages = 1, + TotalRecords = 2, + CurrentPage = 1 + }; + + // Act + var result = querySet.documents; + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count()); + } + + [Fact] + public void DocumentQuerySet_Should_Have_KeywordFilterInfo() + { + // Arrange + var querySet = new DocumentQuerySet + { + documents = new List(), + keywordFilterInfo = new Dictionary> + { + { "Category", new List { "Science", "Technology" } }, + { "Author", new List { "John Doe", "Jane Smith" } } + }, + TotalPages = 1, + TotalRecords = 2, + CurrentPage = 1 + }; + + // Act + var result = querySet.keywordFilterInfo; + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count); + Assert.Contains("Category", result.Keys); + Assert.Contains("Science", result["Category"]); + } + + [Fact] + public void DocumentQuerySet_Should_Have_TotalPages_And_Records() + { + // Arrange + var querySet = new DocumentQuerySet + { + documents = new List(), + keywordFilterInfo = new Dictionary>(), + TotalPages = 5, + TotalRecords = 50, + CurrentPage = 1 + }; + + // Act + var totalPages = querySet.TotalPages; + var totalRecords = querySet.TotalRecords; + + // Assert + Assert.Equal(5, totalPages); + Assert.Equal(50, totalRecords); + } + + [Fact] + public void DocumentQuerySet_Should_Have_CurrentPage() + { + // Arrange + var querySet = new DocumentQuerySet + { + documents = new List(), + keywordFilterInfo = new Dictionary>(), + TotalPages = 5, + TotalRecords = 50, + CurrentPage = 3 + }; + + // Act + var currentPage = querySet.CurrentPage; + + // Assert + Assert.Equal(3, currentPage); + } + + [Fact] + public void DocumentQuerySet_Should_Handle_Empty_DocumentList() + { + // Arrange + var querySet = new DocumentQuerySet + { + documents = new List(), + keywordFilterInfo = new Dictionary>(), + TotalPages = 0, + TotalRecords = 0, + CurrentPage = 1 + }; + + // Act + var result = querySet.documents; + + // Assert + Assert.Empty(result); + } + + [Fact] + public void DocumentQuerySet_Should_Handle_No_KeywordFilters() + { + // Arrange + var querySet = new DocumentQuerySet + { + documents = new List(), + keywordFilterInfo = new Dictionary>(), + TotalPages = 1, + TotalRecords = 2, + CurrentPage = 1 + }; + + // Act + var result = querySet.keywordFilterInfo; + + // Assert + Assert.Empty(result); + } + } + +} diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/UserInterface/PagingRequestValidatorTest.cs b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/UserInterface/PagingRequestValidatorTest.cs new file mode 100644 index 00000000..2d13d72d --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/UserInterface/PagingRequestValidatorTest.cs @@ -0,0 +1,77 @@ +using FluentValidation.TestHelper; +using Microsoft.GS.DPS.Model.UserInterface; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.GS.DPS.Tests.ModelsTest.UserInterface +{ + public class PagingRequestValidatorTest + { + private readonly PagingRequestValidator _validator; + public PagingRequestValidatorTest() + { + _validator = new PagingRequestValidator(); + } + + [Fact] + public void Validate_ValidPagingRequest_ShouldPassValidation() + { + var request = new PagingRequest + { + PageNumber = 1, + PageSize = 10, + StartDate = DateTime.UtcNow.AddDays(-7), + EndDate = DateTime.UtcNow + }; + + var result = _validator.TestValidate(request); + result.ShouldNotHaveAnyValidationErrors(); + } + + [Fact] + public void Validate_PageNumberIsZero_ShouldFailValidation() + { + var request = new PagingRequest + { + PageNumber = 0, + PageSize = 10 + }; + + var result = _validator.TestValidate(request); + result.ShouldHaveValidationErrorFor(r => r.PageNumber); + } + + [Fact] + public void Validate_PageSizeIsNegative_ShouldFailValidation() + { + var request = new PagingRequest + { + PageNumber = 1, + PageSize = -5 + }; + + var result = _validator.TestValidate(request); + result.ShouldHaveValidationErrorFor(r => r.PageSize); + } + + [Fact] + public void Validate_OptionalFieldsNull_ShouldPassValidation() + { + var request = new PagingRequest + { + PageNumber = 1, + PageSize = 10, + StartDate = null, + EndDate = null + }; + + var result = _validator.TestValidate(request); + result.ShouldNotHaveAnyValidationErrors(); + } + } +} + diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/UserInterface/PagingRequestWithSearchValidatorTests.cs b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/UserInterface/PagingRequestWithSearchValidatorTests.cs new file mode 100644 index 00000000..568a68fd --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/ModelsTest/UserInterface/PagingRequestWithSearchValidatorTests.cs @@ -0,0 +1,131 @@ +using FluentValidation.TestHelper; +using Microsoft.GS.DPS.Model.UserInterface; +using System; +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.GS.DPS.Tests.ModelsTest.UserInterface +{ + + public class PagingRequestWithSearchValidatorTests + { + private readonly PagingRequestWithSearchValidator _validator; + + public PagingRequestWithSearchValidatorTests() + { + _validator = new PagingRequestWithSearchValidator(); + } + + [Fact] + public void Should_Have_Error_When_PageNumber_Is_Less_Than_Or_Equal_To_Zero() + { + var model = new PagingRequestWithSearch { PageNumber = 0 }; + var result = _validator.TestValidate(model); + result.ShouldHaveValidationErrorFor(x => x.PageNumber); + } + + [Fact] + public void Should_Have_Error_When_PageSize_Is_Less_Than_Or_Equal_To_Zero() + { + var model = new PagingRequestWithSearch { PageSize = 0 }; + var result = _validator.TestValidate(model); + result.ShouldHaveValidationErrorFor(x => x.PageSize); + } + + [Fact] + public void Should_Pass_When_PageNumber_Is_Greater_Than_Zero() + { + var model = new PagingRequestWithSearch { PageNumber = 1 }; + var result = _validator.TestValidate(model); + result.ShouldNotHaveValidationErrorFor(x => x.PageNumber); + } + + [Fact] + public void Should_Pass_When_PageSize_Is_Greater_Than_Zero() + { + var model = new PagingRequestWithSearch { PageSize = 10 }; + var result = _validator.TestValidate(model); + result.ShouldNotHaveValidationErrorFor(x => x.PageSize); + } + + [Fact] + public void Should_Have_Error_When_StartDate_Is_Later_Than_EndDate() + { + var model = new PagingRequestWithSearch + { + StartDate = new DateTime(2024, 12, 10), + EndDate = new DateTime(2024, 12, 9) + }; + + var result = _validator.TestValidate(model); + result.ShouldHaveValidationErrorFor(x => x.StartDate) + .WithErrorMessage("Start Date cannot be later than End Date"); + } + + [Fact] + public void Should_Not_Have_Error_When_StartDate_Is_Equal_To_EndDate() + { + var model = new PagingRequestWithSearch + { + StartDate = new DateTime(2024, 12, 10), + EndDate = new DateTime(2024, 12, 10) + }; + + var result = _validator.TestValidate(model); + result.ShouldNotHaveValidationErrorFor(x => x.StartDate); + } + + [Fact] + public void Should_Have_Error_When_StartDate_Is_Empty_And_EndDate_Is_Provided() + { + var model = new PagingRequestWithSearch + { + EndDate = new DateTime(2024, 12, 10) + }; + + var result = _validator.TestValidate(model); + result.ShouldHaveValidationErrorFor(x => x.StartDate) + .WithErrorMessage("Start Date cannot be empty when End Date is provided"); + } + + [Fact] + public void Should_Not_Have_Error_When_StartDate_And_EndDate_Are_Both_Null() + { + var model = new PagingRequestWithSearch(); + var result = _validator.TestValidate(model); + result.ShouldNotHaveValidationErrorFor(x => x.StartDate); + } + + [Fact] + public void Should_Not_Have_Error_When_Keyword_Is_Null_Or_Empty() + { + var model = new PagingRequestWithSearch { Keyword = null }; + var result = _validator.TestValidate(model); + result.ShouldNotHaveValidationErrorFor(x => x.Keyword); + } + + [Fact] + public void Should_Pass_When_Tags_Are_Empty() + { + var model = new PagingRequestWithSearch { Tags = new Dictionary() }; + var result = _validator.TestValidate(model); + result.ShouldNotHaveValidationErrorFor(x => x.Tags); + } + + [Fact] + public void Should_Pass_When_Tags_Are_Valid() + { + var model = new PagingRequestWithSearch + { + Tags = new Dictionary + { + { "tag1", "value1" }, + { "tag2", "value2" } + } + }; + + var result = _validator.TestValidate(model); + result.ShouldNotHaveValidationErrorFor(x => x.Tags); + } + } +} diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/Storage/ChatSessions/ChatSessionRepositoryTests.cs b/App/backend-api/Microsoft.GS.DPS.Tests/Storage/ChatSessions/ChatSessionRepositoryTests.cs new file mode 100644 index 00000000..0b54d432 --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/Storage/ChatSessions/ChatSessionRepositoryTests.cs @@ -0,0 +1,49 @@ +using Microsoft.GS.DPS.Storage.ChatSessions.Entities; +using Microsoft.GS.DPS.Storage.Document; +using Microsoft.GS.DPS.Storage.Components; +using MongoDB.Driver; +using Moq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.GS.DPS.Tests.Storage.ChatSessions +{ + public class ChatSessionRepositoryTests + { + private readonly Mock> _mockCollection; + private readonly Mock _mockDatabase; + private readonly DPS.Storage.ChatSessions.ChatSessionRepository _repository; + + public ChatSessionRepositoryTests() + { + _mockCollection = new Mock>(); + _mockDatabase = new Mock(); + _mockDatabase.Setup(db => db.GetCollection(It.IsAny(), It.IsAny())) + .Returns(_mockCollection.Object); + + _repository = new DPS.Storage.ChatSessions.ChatSessionRepository(_mockDatabase.Object, "TestChatSessions"); + } + + [Fact] + public async Task RegisterSessionAsync_ShouldInsertSession_WhenValidSession() + { + // Arrange + var chatSession = new ChatSession + { + SessionId = "123", + // Add other properties as needed + }; + + // Act + await _repository.RegisterSessionAsync(chatSession); + + // Assert + _mockCollection.Verify(m => m.InsertOneAsync(It.IsAny(), null, default), Times.Once); + } + + } +} + diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Component/CosmosDBEntityBaseTests.cs b/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Component/CosmosDBEntityBaseTests.cs new file mode 100644 index 00000000..db50a66d --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Component/CosmosDBEntityBaseTests.cs @@ -0,0 +1,67 @@ +using Microsoft.GS.DPS.Storage.Components; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.GS.DPS.Tests.Storage.Component +{ + public class CosmosDBEntityBaseTests + { + [Fact] + public void ShouldGenerateValidGuid() + { + // Arrange + var entity = new CosmosDBEntityBase(); + + // Act + var id = entity.id; + + // Assert + Assert.True(Guid.TryParse(id.ToString(), out _), "ID is not a valid GUID"); + } + + [Fact] + public void ShouldGenerateValidPartitionKey() + { + // Arrange + var entity = new CosmosDBEntityBase(); + + // Act + var partitionKey = entity.__partitionkey; + + // Assert + Assert.NotNull(partitionKey); + Assert.Matches(@"^\d{4}$", partitionKey); // Partition key padded to 4 digits for 9999 partitions + } + + [Fact] + public void PartitionKeyGenerationShouldBeDeterministic() + { + // Arrange + var id = Guid.Parse("550e8400-e29b-41d4-a716-446655440000"); + + // Act + var key1 = CosmosDBEntityBase.GetKey(id, 9999); + var key2 = CosmosDBEntityBase.GetKey(id, 9999); + + // Assert + Assert.Equal(key1, key2); + } + + [Fact] + public void PartitionKeyShouldChangeWithDifferentNumberOfPartitions() + { + // Arrange + var id = Guid.Parse("550e8400-e29b-41d4-a716-446655440000"); + + // Act + var key1 = CosmosDBEntityBase.GetKey(id, 100); + var key2 = CosmosDBEntityBase.GetKey(id, 9999); + + // Assert + Assert.NotEqual(key1, key2); + } + } +} diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Component/GenericSpecificationTests.cs b/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Component/GenericSpecificationTests.cs new file mode 100644 index 00000000..9d4e7ee9 --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Component/GenericSpecificationTests.cs @@ -0,0 +1,113 @@ +using Microsoft.GS.DPS.Storage.Components; +using System; +using System.Linq; +using System.Linq.Expressions; +using Xunit; + +namespace Microsoft.GS.DPS.Tests.Storage.Component +{ + public class GenericSpecificationTests + { + // Test the constructor for initializing Predicate, OrderBy, and Order properties + [Fact] + public void Constructor_ShouldInitializePropertiesCorrectly() + { + // Arrange + Expression> predicate = e => e.Id > 5; + Expression> orderBy = e => e.Name; + + // Act + var specification = new GenericSpecification(predicate, orderBy, Order.Desc); + + // Assert + Assert.NotNull(specification.Predicate); + Assert.NotNull(specification.OrderBy); + Assert.Equal(Order.Desc, specification.Order); + Assert.Equal(predicate, specification.Predicate); + Assert.Equal(orderBy, specification.OrderBy); + } + + // Test Predicate filtering logic + [Fact] + public void Predicate_ShouldFilterEntitiesCorrectly() + { + // Arrange + Expression> predicate = e => e.Id > 5; + var specification = new GenericSpecification(predicate); + + var entities = new[] + { + new TestEntityForSpecification { Id = 4, Name = "Entity1" }, + new TestEntityForSpecification { Id = 6, Name = "Entity2" }, + new TestEntityForSpecification { Id = 8, Name = "Entity3" } + }; + + // Act + var filteredEntities = entities.AsQueryable().Where(specification.Predicate).ToList(); + + // Assert + Assert.Equal(2, filteredEntities.Count); + Assert.Contains(filteredEntities, e => e.Id == 6); + Assert.Contains(filteredEntities, e => e.Id == 8); + } + + // Test sorting by OrderBy with Order.Asc (ascending order) + [Fact] + public void OrderBy_ShouldReturnCorrectOrder_Asc() + { + // Arrange + Expression> orderBy = e => e.Name; + var specification = new GenericSpecification(e => e.Id > 0, orderBy, Order.Asc); + + var entities = new[] + { + new TestEntityForSpecification { Id = 1, Name = "Charlie" }, + new TestEntityForSpecification { Id = 2, Name = "Alice" }, + new TestEntityForSpecification { Id = 3, Name = "Bob" } + }; + + // Act + var orderedEntities = entities.AsQueryable() + .OrderBy(specification.OrderBy) // Ascending order + .ToList(); + + // Assert + Assert.Equal("Alice", orderedEntities[0].Name); + Assert.Equal("Bob", orderedEntities[1].Name); + Assert.Equal("Charlie", orderedEntities[2].Name); + } + + // Test sorting by OrderBy with Order.Desc (descending order) + [Fact] + public void OrderBy_ShouldReturnCorrectOrder_Desc() + { + // Arrange + Expression> orderBy = e => e.Name; + var specification = new GenericSpecification(e => e.Id > 0, orderBy, Order.Desc); + + var entities = new[] + { + new TestEntityForSpecification { Id = 1, Name = "Charlie" }, + new TestEntityForSpecification { Id = 2, Name = "Alice" }, + new TestEntityForSpecification { Id = 3, Name = "Bob" } + }; + + // Act + var orderedEntities = entities.AsQueryable() + .OrderByDescending(specification.OrderBy) // Descending order + .ToList(); + + // Assert + Assert.Equal("Charlie", orderedEntities[0].Name); + Assert.Equal("Bob", orderedEntities[1].Name); + Assert.Equal("Alice", orderedEntities[2].Name); + } + } + + // Renamed test entity to avoid conflicts + public class TestEntityForSpecification + { + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Component/MongoEntityCollectionBaseTests.cs b/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Component/MongoEntityCollectionBaseTests.cs new file mode 100644 index 00000000..615f99e3 --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Component/MongoEntityCollectionBaseTests.cs @@ -0,0 +1,63 @@ +using System; +using Xunit; +using MongoDB.Driver; +using Microsoft.GS.DPS.Storage.Components; + +namespace Microsoft.GS.DPS.Tests.Storage.Component +{ + public class MongoEntityCollectionBaseTests + { + [Fact] + public void Constructor_ValidInputs_ShouldInitializeEntityCollection() + { + // Arrange + string validConnectionString = "mongodb://localhost:27017"; + string collectionName = "TestCollection"; + + // Act + var repository = new MongoEntntyCollectionBase(validConnectionString, collectionName); + + // Assert + Assert.NotNull(repository.EntityCollection); + Assert.IsType>(repository.EntityCollection); + } + [Fact] + public void Constructor_InvalidConnectionString_ShouldThrowMongoConfigurationException() + { + // Arrange + string invalidConnectionString = "invalid://localhost:27017"; + + // Act & Assert + Assert.Throws(() => + { + var client = new MongoDB.Driver.MongoClient(invalidConnectionString); + client.ListDatabaseNames(); // Triggers the exception + }); + } + + + + [Fact] + public void CosmosMongoClientManager_Instance_ShouldBeSingleton() + { + // Arrange + CosmosMongoClientManager.DataconnectionString = "mongodb://localhost:27017"; + + // Act + var client1 = CosmosMongoClientManager.Instance; + var client2 = CosmosMongoClientManager.Instance; + + // Assert + Assert.NotNull(client1); + Assert.Same(client1, client2); // Ensure singleton + } + } + + // Mock entity implementing IEntityModel for testing + public class TestEntity : IEntityModel + { + public string Id { get; set; } + string IEntityModel.id { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + string IEntityModel.__partitionkey { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + } +} diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Document/DocumentTests.cs b/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Document/DocumentTests.cs new file mode 100644 index 00000000..e7fb9f7e --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Document/DocumentTests.cs @@ -0,0 +1,146 @@ +using Microsoft.GS.DPS.Storage.Document.Entities; +using System; +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.GS.DPS.Tests.Storage.Document.Entities +{ + public class DocumentTests + { + [Fact] + public void Document_ShouldInitializeWithDefaultValues() + { + // Arrange & Act + var document = new Microsoft.GS.DPS.Storage.Document.Entities.Document(); + + // Assert + Assert.Null(document.DocumentId); + Assert.Null(document.FileName); + Assert.Null(document.Keywords); + Assert.Equal(default(DateTime), document.ImportedTime); + Assert.Equal(default(TimeSpan), document.ProcessingTime); + Assert.Null(document.MimeType); + Assert.Null(document.Summary); + } + + [Fact] + public void Document_ShouldSetAndGetProperties() + { + // Arrange + var document = new Microsoft.GS.DPS.Storage.Document.Entities.Document(); + var documentId = "12345"; + var fileName = "example.pdf"; + var keywords = new Dictionary + { + { "Topic", "Science" }, + { "Category", "Physics" } + }; + var importedTime = DateTime.UtcNow; + var processingTime = TimeSpan.FromSeconds(120); + var mimeType = "application/pdf"; + var summary = "This is a summary of the document."; + + // Act + document.DocumentId = documentId; + document.FileName = fileName; + document.Keywords = keywords; + document.ImportedTime = importedTime; + document.ProcessingTime = processingTime; + document.MimeType = mimeType; + document.Summary = summary; + + // Assert + Assert.Equal(documentId, document.DocumentId); + Assert.Equal(fileName, document.FileName); + Assert.Equal(keywords, document.Keywords); + Assert.Equal(importedTime, document.ImportedTime); + Assert.Equal(processingTime, document.ProcessingTime); + Assert.Equal(mimeType, document.MimeType); + Assert.Equal(summary, document.Summary); + } + + [Fact] + public void Document_KeywordsCanHandleEmptyDictionary() + { + // Arrange + var document = new Microsoft.GS.DPS.Storage.Document.Entities.Document(); + var keywords = new Dictionary(); + + // Act + document.Keywords = keywords; + + // Assert + Assert.NotNull(document.Keywords); + Assert.Empty(document.Keywords); + } + + [Fact] + public void Document_ImportedTime_ShouldSetCorrectValue() + { + // Arrange + var document = new Microsoft.GS.DPS.Storage.Document.Entities.Document(); + var importedTime = new DateTime(2024, 5, 1, 12, 0, 0); + + // Act + document.ImportedTime = importedTime; + + // Assert + Assert.Equal(importedTime, document.ImportedTime); + } + + [Fact] + public void Document_ProcessingTime_ShouldSetCorrectValue() + { + // Arrange + var document = new Microsoft.GS.DPS.Storage.Document.Entities.Document(); + var processingTime = TimeSpan.FromMinutes(5); + + // Act + document.ProcessingTime = processingTime; + + // Assert + Assert.Equal(processingTime, document.ProcessingTime); + } + + [Fact] + public void Document_HandlesNullValuesInKeywords() + { + // Arrange + var document = new Microsoft.GS.DPS.Storage.Document.Entities.Document(); + + // Act + document.Keywords = null; + + // Assert + Assert.Null(document.Keywords); + } + + [Fact] + public void Document_FileName_ShouldSetAndReturnCorrectValue() + { + // Arrange + var document = new Microsoft.GS.DPS.Storage.Document.Entities.Document(); + var fileName = "testfile.docx"; + + // Act + document.FileName = fileName; + + // Assert + Assert.Equal(fileName, document.FileName); + } + + [Fact] + public void Document_MimeType_ShouldSetAndReturnCorrectValue() + { + // Arrange + var document = new Microsoft.GS.DPS.Storage.Document.Entities.Document(); + var mimeType = "text/plain"; + + // Act + document.MimeType = mimeType; + + // Assert + Assert.Equal(mimeType, document.MimeType); + } + } +} diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Document/KeywordsSerializerTests.cs b/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Document/KeywordsSerializerTests.cs new file mode 100644 index 00000000..1a067ea2 --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Document/KeywordsSerializerTests.cs @@ -0,0 +1,173 @@ +using Microsoft.GS.DPS.Storage.Document; +using Microsoft.GS.DPS.Storage.Document.Entities; // Correct namespace for Document class +using MongoDB.Bson; +using MongoDB.Bson.IO; +using MongoDB.Bson.Serialization; +using System; +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.GS.DPS.Tests.Storage.Document +{ + public class KeywordsSerializerTests + { + [Fact] + public void Serialize_ShouldCorrectlySerializeKeywords() + { + // Arrange + var serializer = new KeywordsSerializer(); + var keywords = new List>> + { + new Dictionary> + { + { "topic1", new List { "keyword1", "keyword2" } }, + { "topic2", new List { "keyword3" } } + } + }; + + // Act + var bsonArray = new BsonArray(); + foreach (var keywordDict in keywords) + { + var bsonDocument = new BsonDocument(); + foreach (var kvp in keywordDict) + { + bsonDocument.Add(kvp.Key, new BsonArray(kvp.Value)); + } + bsonArray.Add(bsonDocument); + } + + // Assert + Assert.NotNull(bsonArray); + Assert.Single(bsonArray); + var document = bsonArray[0].AsBsonDocument; + Assert.True(document.Contains("topic1")); + Assert.True(document.Contains("topic2")); + Assert.Equal(new BsonArray { "keyword1", "keyword2" }, document["topic1"].AsBsonArray); + Assert.Equal(new BsonArray { "keyword3" }, document["topic2"].AsBsonArray); + } + + [Fact] + public void Deserialize_ShouldCorrectlyDeserializeKeywords() + { + // Arrange + var serializer = new KeywordsSerializer(); + + // Ensure BSON matches expected structure + var bsonArray = new BsonArray + { + new BsonDocument + { + { "topic1", new BsonArray { "keyword1", "keyword2" } }, + { "topic2", new BsonArray { "keyword3" } } + } + }; + + var bsonDocument = new BsonDocument { { "Keywords", bsonArray } }; + + using (var bsonReader = new BsonDocumentReader(bsonDocument)) + { + var context = BsonDeserializationContext.CreateRoot(bsonReader); + bsonReader.ReadStartDocument(); // Move the reader to the "Keywords" field + bsonReader.ReadName("Keywords"); // Read the array field + var result = serializer.Deserialize(context, default); + + // Act & Assert + Assert.NotNull(result); + Assert.Single(result); + + var dict = result[0]; + Assert.True(dict.ContainsKey("topic1")); + Assert.True(dict.ContainsKey("topic2")); + Assert.Equal(new List { "keyword1", "keyword2" }, dict["topic1"]); + Assert.Equal(new List { "keyword3" }, dict["topic2"]); + } + } + + [Fact] + public void Serialize_ShouldHandleEmptyKeywordsList() + { + // Arrange + var serializer = new KeywordsSerializer(); + var keywords = new List>>(); + + // Act + var bsonArray = new BsonArray(); + foreach (var keywordDict in keywords) + { + var bsonDocument = new BsonDocument(); + foreach (var kvp in keywordDict) + { + bsonDocument.Add(kvp.Key, new BsonArray(kvp.Value)); + } + bsonArray.Add(bsonDocument); + } + + // Assert + Assert.NotNull(bsonArray); + Assert.Empty(bsonArray); // Should be empty as no keywords were provided + } + + [Fact] + public void Deserialize_ShouldHandleEmptyKeywordsList() + { + // Arrange + var serializer = new KeywordsSerializer(); + + // Create an empty BSON array + var bsonArray = new BsonArray(); + + var bsonDocument = new BsonDocument { { "Keywords", bsonArray } }; + + using (var bsonReader = new BsonDocumentReader(bsonDocument)) + { + var context = BsonDeserializationContext.CreateRoot(bsonReader); + bsonReader.ReadStartDocument(); // Move the reader to the "Keywords" field + bsonReader.ReadName("Keywords"); // Read the array field + var result = serializer.Deserialize(context, default); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); // Should be empty as no keywords were deserialized + } + } + + [Fact] + public void Deserialize_ShouldHandleInvalidBsonStructure() + { + // Arrange + var serializer = new KeywordsSerializer(); + + // Create an invalid BSON structure (e.g., "Keywords" is not an array or is malformed) + var bsonDocument = new BsonDocument + { + { "Keywords", new BsonDocument { { "InvalidKey", "InvalidValue" } } } // Invalid structure + }; + + using (var bsonReader = new BsonDocumentReader(bsonDocument)) + { + var context = BsonDeserializationContext.CreateRoot(bsonReader); + + // Act & Assert + var exception = Assert.Throws(() => serializer.Deserialize(context, default)); + Assert.Contains("ReadStartArray can only be called when CurrentBsonType is Array", exception.Message); + } + } + } + public class MongoDbConfigTests + { + //[Fact] + //public void RegisterClassMaps_ShouldRegisterDocumentClassMap() + //{ + // // Arrange + // MongoDbConfig.RegisterClassMaps(); // Register class maps for Document type + + // // Act + // bool classMapRegistered = BsonClassMap.IsClassMapRegistered(typeof(Microsoft.GS.DPS.Storage.Document.Entities.Document)); + + // // Assert + // Assert.True(classMapRegistered, "Document class map was not registered properly."); + //} + } +} + diff --git a/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Document/QueryResultSetTest.cs b/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Document/QueryResultSetTest.cs new file mode 100644 index 00000000..0486d865 --- /dev/null +++ b/App/backend-api/Microsoft.GS.DPS.Tests/Storage/Document/QueryResultSetTest.cs @@ -0,0 +1,77 @@ +using Microsoft.GS.DPS.Storage.Document; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.GS.DPS.Tests.Storage.Document +{ + public class QueryResultSetTest + { + [Fact] + public void QueryResultSet_ShouldInitializeProperties() + { + // Arrange & Act + var queryResultSet = new QueryResultSet(); + + // Assert + Assert.Null(queryResultSet.Results); // Default value for a property of type IEnumerable + Assert.Equal(0, queryResultSet.TotalPages); // Default value for int + Assert.Equal(0, queryResultSet.TotalRecords); // Default value for int + Assert.Equal(0, queryResultSet.CurrentPage); // Default value for int + } + + [Fact] + public void QueryResultSet_ShouldAllowPropertyAssignment() + { + // Arrange + var documents = new List + { + new DPS.Storage.Document.Entities.Document { DocumentId = "1", FileName = "Test Document 1" }, + new DPS.Storage.Document.Entities.Document { DocumentId = "2", FileName = "Test Document 2" } + }; + + var expectedTotalPages = 5; + var expectedTotalRecords = 50; + var expectedCurrentPage = 2; + + // Act + var queryResultSet = new QueryResultSet + { + Results = documents, + TotalPages = expectedTotalPages, + TotalRecords = expectedTotalRecords, + CurrentPage = expectedCurrentPage + }; + + // Assert + Assert.Equal(documents, queryResultSet.Results); + Assert.Equal(expectedTotalPages, queryResultSet.TotalPages); + Assert.Equal(expectedTotalRecords, queryResultSet.TotalRecords); + Assert.Equal(expectedCurrentPage, queryResultSet.CurrentPage); + } + + [Fact] + public void QueryResultSet_ShouldHandleEmptyResults() + { + // Arrange + var emptyResults = new List(); + + // Act + var queryResultSet = new QueryResultSet + { + Results = emptyResults, + TotalPages = 0, + TotalRecords = 0, + CurrentPage = 0 + }; + + // Assert + Assert.Empty(queryResultSet.Results); // Ensure Results is empty + Assert.Equal(0, queryResultSet.TotalPages); + Assert.Equal(0, queryResultSet.TotalRecords); + Assert.Equal(0, queryResultSet.CurrentPage); + } + } +} diff --git a/App/backend-api/Microsoft.GS.DPS.sln b/App/backend-api/Microsoft.GS.DPS.sln index 005f4e33..1dcd90e7 100644 --- a/App/backend-api/Microsoft.GS.DPS.sln +++ b/App/backend-api/Microsoft.GS.DPS.sln @@ -3,9 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.12.35209.166 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.GS.DPS", "Microsoft.GS.DPS\Microsoft.GS.DPS.csproj", "{E0837665-8C18-47CF-BA9F-742E97CCDC18}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.GS.DPS", "Microsoft.GS.DPS\Microsoft.GS.DPS.csproj", "{E0837665-8C18-47CF-BA9F-742E97CCDC18}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.GS.DPS.Host", "Microsoft.GS.DPS.Host\Microsoft.GS.DPS.Host.csproj", "{3BBCDD67-966B-442A-9A34-FE6D311B4824}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.GS.DPS.Host", "Microsoft.GS.DPS.Host\Microsoft.GS.DPS.Host.csproj", "{3BBCDD67-966B-442A-9A34-FE6D311B4824}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.GS.DPS.Tests", "Microsoft.GS.DPS.Tests\Microsoft.GS.DPS.Tests.csproj", "{BA30D8AB-70AD-4015-BE81-B671501DE512}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -21,6 +23,10 @@ Global {3BBCDD67-966B-442A-9A34-FE6D311B4824}.Debug|Any CPU.Build.0 = Debug|Any CPU {3BBCDD67-966B-442A-9A34-FE6D311B4824}.Release|Any CPU.ActiveCfg = Release|Any CPU {3BBCDD67-966B-442A-9A34-FE6D311B4824}.Release|Any CPU.Build.0 = Release|Any CPU + {BA30D8AB-70AD-4015-BE81-B671501DE512}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA30D8AB-70AD-4015-BE81-B671501DE512}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA30D8AB-70AD-4015-BE81-B671501DE512}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA30D8AB-70AD-4015-BE81-B671501DE512}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Deployment/resourcedeployment.ps1 b/Deployment/resourcedeployment.ps1 index ced60993..35a5342a 100644 --- a/Deployment/resourcedeployment.ps1 +++ b/Deployment/resourcedeployment.ps1 @@ -41,6 +41,35 @@ function successBanner(){ Write-Host " |_| |___/ " } +function failureBanner(){ + Write-Host " _____ _ _ " + Write-Host "| __ \ | | | | " + Write-Host "| | | | ___ _ __ | | ___ _ _ _ __ ___ ___ _ __ | |_ " + Write-Host "| | | |/ _ \ '_ \| |/ _ \| | | | '_ ` _ \ / _ \ '_ \| __| " + Write-Host "| |__| | __/ |_) | | (_) | |_| | | | | | | __/ | | | |_ " + Write-Host "|_____/ \___| .__/|_|\___/ \__, |_| |_| |_|\___|_| |_|\__| " + Write-Host " | | __/ | " + Write-Host " ______ _|_| _ |___/ " + Write-Host "| ____| (_) | | | " + Write-Host "| |__ __ _ _| | ___ __| | " + Write-Host "| __/ _` | | |/ _ \/ _` | " + Write-Host "| | | (_| | | | __/ (_| | " + Write-Host "|_| \__,_|_|_|\___|\__,_| " +} + +# Common function to check if a variable is null or empty +function ValidateVariableIsNullOrEmpty { + param ( + [string]$variableValue, + [string]$variableName + ) + + if ([string]::IsNullOrEmpty($variableValue)) { + Write-Host "Error: $variableName is null or empty." -ForegroundColor Red + failureBanner + exit 1 + } +} # Function to prompt for parameters with kind messages function PromptForParameters { param( @@ -395,10 +424,33 @@ try { ############################################################### # Get the storage account key $storageAccountKey = az storage account keys list --account-name $deploymentResult.StorageAccountName --resource-group $deploymentResult.ResourceGroupName --query "[0].value" -o tsv + + # Validate if the storage account key is empty or null + ValidateVariableIsNullOrEmpty -variableValue $storageAccountKey -variableName "Storage account key" + ## Construct the connection string manually $storageAccountConnectionString = "DefaultEndpointsProtocol=https;AccountName=$($deploymentResult.StorageAccountName);AccountKey=$storageAccountKey;EndpointSuffix=core.windows.net" + # Validate if the Storage Account Connection String is empty or null + ValidateVariableIsNullOrEmpty -variableValue $storageAccountConnectionString -variableName "Storage Account Connection String" + ## Assign the connection string to the deployment result object - $deploymentResult.StorageAccountConnectionString = $storageAccountConnectionString + $deploymentResult.StorageAccountConnectionString = $storageAccountConnectionString + + # Check if ResourceGroupName is valid + ValidateVariableIsNullOrEmpty -variableValue $deploymentResult.ResourceGroupName -variableName "Resource group name" + + # Check if AzCosmosDBName is valid + ValidateVariableIsNullOrEmpty -variableValue $deploymentResult.AzCosmosDBName -variableName "Az Cosmos DB name" + + # Check if AzCognitiveServiceName is valid + ValidateVariableIsNullOrEmpty -variableValue $deploymentResult.AzCognitiveServiceName -variableName "Az Cognitive Service name" + + # Check if AzSearchServiceName is valid + ValidateVariableIsNullOrEmpty -variableValue $deploymentResult.AzSearchServiceName -variableName "Az Search Service name" + + # Check if AzOpenAiServiceName is valid + ValidateVariableIsNullOrEmpty -variableValue $deploymentResult.AzOpenAiServiceName -variableName "Az OpenAI Service name" + # Get MongoDB connection string $deploymentResult.AzCosmosDBConnectionString = az cosmosdb keys list --name $deploymentResult.AzCosmosDBName --resource-group $deploymentResult.ResourceGroupName --type connection-strings --query "connectionStrings[0].connectionString" -o tsv # Get Azure Cognitive Service API Key @@ -601,26 +653,49 @@ try { # 6-1. Get Az Network resource Name with the public IP address Write-Host "Assign DNS Name to the public IP address" -ForegroundColor Green $publicIpName=$(az network public-ip list --query "[?ipAddress=='$externalIP'].name" --output tsv) - # 6-2. Generate Unique backend API fqdn Name - esgdocanalysis-3 digit random number with padding 0 $dnsName = "kmgs$($(Get-Random -Minimum 0 -Maximum 9999).ToString("D4"))" + + # Validate if the AKS Resource Group Name, Public IP name and DNS Name are provided + ValidateVariableIsNullOrEmpty -variableValue $aksResourceGroupName -variableName "AKS Resource Group name" + + ValidateVariableIsNullOrEmpty -variableValue $publicIpName -variableName "Public IP name" + ValidateVariableIsNullOrEmpty -variableValue $dnsName -variableName "DNS Name" + # 6-3. Assign DNS Name to the public IP address az network public-ip update --resource-group $aksResourceGroupName --name $publicIpName --dns-name $dnsName - # 6-4. Get FQDN for the public IP address - $fqdn = az network public-ip show --resource-group $aksResourceGroupName --name $publicIpName --query "dnsSettings.fqdn" --output tsv - Write-Host "FQDN for the public IP address is: $fqdn" -ForegroundColor Green + # 6-4. Get FQDN for the public IP address + $fqdn = az network public-ip show --resource-group $aksResourceGroupName --name $publicIpName --query "dnsSettings.fqdn" --output tsv + + # Validate if the FQDN is null or empty + ValidateVariableIsNullOrEmpty -variableValue $fqdn -variableName "FQDN" + # 7. Assign the role for aks system assigned managed identity to App Configuration Data Reader role with the scope of Resourcegroup Write-Host "Assign the role for aks system assigned managed identity to App Configuration Data Reader role" -ForegroundColor Green + # Ensure that the required fields are not null or empty + ValidateVariableIsNullOrEmpty -variableValue $deploymentResult.ResourceGroupName -variableName "Resource group name" + + ValidateVariableIsNullOrEmpty -variableValue $deploymentResult.AksName -variableName "AKS cluster name" + # Get vmss resource group name $vmssResourceGroupName = $(az aks show --resource-group $deploymentResult.ResourceGroupName --name $deploymentResult.AksName --query nodeResourceGroup --output tsv) + + # Validate if vmss Resource Group Name is null or empty + ValidateVariableIsNullOrEmpty -variableValue $vmssResourceGroupName -variableName "VMSS resource group" + # Get vmss name $vmssName = $(az vmss list --resource-group $vmssResourceGroupName --query "[0].name" --output tsv) + + # Validate if vmss Name is null or empty + ValidateVariableIsNullOrEmpty -variableValue $vmssName -variableName "VMSS name" + # Create System Assigned Managed Identity $systemAssignedIdentity = $(az vmss identity assign --resource-group $vmssResourceGroupName --name $vmssName --query systemAssignedIdentity --output tsv) - + # Validate if System Assigned Identity is null or empty + ValidateVariableIsNullOrEmpty -variableValue $systemAssignedIdentity -variableName "System-assigned managed identity" # Assign the role for aks system assigned managed identity to App Configuration Data Reader role with the scope of Resourcegroup az role assignment create --assignee $systemAssignedIdentity --role "App Configuration Data Reader" --scope $deploymentResult.ResourceGroupId