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