From 1a8b2d3bc76d98b4bd555e3bde9e55c878856507 Mon Sep 17 00:00:00 2001 From: Ryan Dudley Date: Thu, 20 Feb 2025 16:18:15 -0500 Subject: [PATCH 1/6] inital adding test --- DatabaseProjectAPI.sln | 9 +++++ DatabaseProjectAPI/DatabaseProjectAPI.csproj | 1 + XUnitTest/AutoDeleteActionTest.cs | 42 ++++++++++++++++++++ XUnitTest/XUnitTest.csproj | 27 +++++++++++++ 4 files changed, 79 insertions(+) create mode 100644 XUnitTest/AutoDeleteActionTest.cs create mode 100644 XUnitTest/XUnitTest.csproj diff --git a/DatabaseProjectAPI.sln b/DatabaseProjectAPI.sln index 4b67481..6710ed8 100644 --- a/DatabaseProjectAPI.sln +++ b/DatabaseProjectAPI.sln @@ -7,6 +7,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DatabaseProjectAPI", "Datab EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KubsConnect", "KubsConnect\KubsConnect.csproj", "{3ED8AE85-9161-47E3-95AC-B4A224D49AAA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XUnitTest", "XUnitTest\XUnitTest.csproj", "{63016646-DDEC-41FA-92A1-4F33E9EAD8CE}" + ProjectSection(ProjectDependencies) = postProject + {BCFC56D8-061F-4C1C-AA30-41E784E4AE13} = {BCFC56D8-061F-4C1C-AA30-41E784E4AE13} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +26,10 @@ Global {3ED8AE85-9161-47E3-95AC-B4A224D49AAA}.Debug|Any CPU.Build.0 = Debug|Any CPU {3ED8AE85-9161-47E3-95AC-B4A224D49AAA}.Release|Any CPU.ActiveCfg = Release|Any CPU {3ED8AE85-9161-47E3-95AC-B4A224D49AAA}.Release|Any CPU.Build.0 = Release|Any CPU + {63016646-DDEC-41FA-92A1-4F33E9EAD8CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63016646-DDEC-41FA-92A1-4F33E9EAD8CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63016646-DDEC-41FA-92A1-4F33E9EAD8CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63016646-DDEC-41FA-92A1-4F33E9EAD8CE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/DatabaseProjectAPI/DatabaseProjectAPI.csproj b/DatabaseProjectAPI/DatabaseProjectAPI.csproj index 5ca2d53..73dba2c 100644 --- a/DatabaseProjectAPI/DatabaseProjectAPI.csproj +++ b/DatabaseProjectAPI/DatabaseProjectAPI.csproj @@ -15,6 +15,7 @@ + diff --git a/XUnitTest/AutoDeleteActionTest.cs b/XUnitTest/AutoDeleteActionTest.cs new file mode 100644 index 0000000..6aa2e2f --- /dev/null +++ b/XUnitTest/AutoDeleteActionTest.cs @@ -0,0 +1,42 @@ +using DatabaseProjectAPI.Actions; +using DatabaseProjectAPI.DataContext; +using DatabaseProjectAPI.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Moq; + +namespace XUnitTest; + +public class AutoDeleteActionTest +{ + private List MockStockHistoryList() + { + return new List + { + new StockHistory {HistoryId=1, Timestamp = DateTime.UtcNow.AddDays(-95)}, + new StockHistory {HistoryId =2, Timestamp = DateTime.UtcNow.AddDays(-100)} + + }; + } + + [Fact] + public async Task DeleteOldStockHistoryTest_ShouldDelete() + { + var mockDbContext = new Mock(); + var mockLogger = new Mock>(); + var cancellationToken = new CancellationToken(); + var service = new AutoDeleteAction(mockDbContext.Object, mockLogger.Object); + + var stockHistoryList = MockStockHistoryList(); + var mockStockHistoryDbSet = new Mock>(); + + mockDbContext.Setup(db => db.StockHistories).Returns(mockStockHistoryDbSet.Object); + mockStockHistoryDbSet.Setup(db => db.ExecuteDeleteAsync(cancellationToken)).ReturnsAsync(stockHistoryList.Count); + + var result = service.DeleteOldStockHistoryAsync(cancellationToken); + + Assert.NotNull(result); + + mockLogger.Verify(x => x.LogInformation("{Count} old stock history records deleted.", stockHistoryList.Count), Times.Once); + } +} \ No newline at end of file diff --git a/XUnitTest/XUnitTest.csproj b/XUnitTest/XUnitTest.csproj new file mode 100644 index 0000000..e7e8a43 --- /dev/null +++ b/XUnitTest/XUnitTest.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + From 823303d3c03d1c09d9e66d42a22447decdfc57f2 Mon Sep 17 00:00:00 2001 From: Ryan Dudley Date: Fri, 4 Apr 2025 14:12:04 -0400 Subject: [PATCH 2/6] XUNIT REVIEWS --- .../Actions/AutoDeleteAction.cs | 131 +++++++++--------- XUnitTest/ActionTests/AutoDeleteActionTest.cs | 98 +++++++++++++ XUnitTest/ActionTests/EventActionTests.cs | 82 +++++++++++ .../ActionTests/MarketNewsActionTests.cs | 88 ++++++++++++ XUnitTest/ActionTests/StockActionTests.cs | 75 ++++++++++ .../ActionTests/StockHistoryActionTests.cs | 68 +++++++++ .../ActionTests/TrackedStockActionTests.cs | 70 ++++++++++ XUnitTest/AutoDeleteActionTest.cs | 42 ------ .../ServiceTests/AlphaVantageServiceTests.cs | 111 +++++++++++++++ .../DataCleanupBackgroundServiceTests.cs | 44 ++++++ .../EventsBackgroundServiceTests.cs | 71 ++++++++++ XUnitTest/ServiceTests/FinnhubServiceTests.cs | 88 ++++++++++++ XUnitTest/ServiceTests/NewsAPIServiceTests.cs | 73 ++++++++++ .../NewsBackgroundServiceTests.cs | 63 +++++++++ .../StockQuoteBackgroundServiceTests.cs | 76 ++++++++++ XUnitTest/XUnitTest.csproj | 2 + 16 files changed, 1073 insertions(+), 109 deletions(-) create mode 100644 XUnitTest/ActionTests/AutoDeleteActionTest.cs create mode 100644 XUnitTest/ActionTests/EventActionTests.cs create mode 100644 XUnitTest/ActionTests/MarketNewsActionTests.cs create mode 100644 XUnitTest/ActionTests/StockActionTests.cs create mode 100644 XUnitTest/ActionTests/StockHistoryActionTests.cs create mode 100644 XUnitTest/ActionTests/TrackedStockActionTests.cs delete mode 100644 XUnitTest/AutoDeleteActionTest.cs create mode 100644 XUnitTest/ServiceTests/AlphaVantageServiceTests.cs create mode 100644 XUnitTest/ServiceTests/DataCleanupBackgroundServiceTests.cs create mode 100644 XUnitTest/ServiceTests/EventsBackgroundServiceTests.cs create mode 100644 XUnitTest/ServiceTests/FinnhubServiceTests.cs create mode 100644 XUnitTest/ServiceTests/NewsAPIServiceTests.cs create mode 100644 XUnitTest/ServiceTests/NewsBackgroundServiceTests.cs create mode 100644 XUnitTest/ServiceTests/StockQuoteBackgroundServiceTests.cs diff --git a/DatabaseProjectAPI/Actions/AutoDeleteAction.cs b/DatabaseProjectAPI/Actions/AutoDeleteAction.cs index 8677d6d..8f31a33 100644 --- a/DatabaseProjectAPI/Actions/AutoDeleteAction.cs +++ b/DatabaseProjectAPI/Actions/AutoDeleteAction.cs @@ -1,91 +1,88 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using DatabaseProjectAPI.DataContext; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; +using DatabaseProjectAPI.DataContext; -namespace DatabaseProjectAPI.Actions +namespace DatabaseProjectAPI.Actions; + +public interface IAutoDeleteService +{ + Task DeleteOldStockHistoryAsync(CancellationToken cancellationToken); + Task DeleteOldApiCallLogsAsync(CancellationToken cancellationToken); +} + +public class AutoDeleteAction : IAutoDeleteService { - public interface IAutoDeleteService + private readonly DpapiDbContext _dbContext; + private readonly ILogger _logger; + + public AutoDeleteAction(DpapiDbContext dbContext, ILogger logger) { - Task DeleteOldStockHistoryAsync(CancellationToken cancellationToken); - Task DeleteOldApiCallLogsAsync(CancellationToken cancellationToken); + _dbContext = dbContext; + _logger = logger; } - public class AutoDeleteAction : IAutoDeleteService + public async Task DeleteOldStockHistoryAsync(CancellationToken cancellationToken) { - private readonly DpapiDbContext _dbContext; - private readonly ILogger _logger; + var ninetyDaysAgo = DateTime.UtcNow.AddDays(-90); - public AutoDeleteAction(DpapiDbContext dbContext, ILogger logger) + try { - _dbContext = dbContext; - _logger = logger; - } + + var oldHistories = await _dbContext.StockHistories + .Where(sh => sh.Timestamp < ninetyDaysAgo) + .ToListAsync(cancellationToken); - public async Task DeleteOldStockHistoryAsync(CancellationToken cancellationToken) - { - var ninetyDaysAgo = DateTime.UtcNow.AddDays(-90); - - try - { - - int deletedCount = await _dbContext.StockHistories - .Where(sh => sh.Timestamp < ninetyDaysAgo) - .ExecuteDeleteAsync(cancellationToken); - - if (deletedCount > 0) - { - _logger.LogInformation("{Count} old stock history records deleted.", deletedCount); - } - else - { - _logger.LogInformation("No stock history records found to delete."); - } - } - catch (OperationCanceledException) + if (oldHistories.Any()) { - _logger.LogInformation("Deletion of old stock history was cancelled."); - throw; + _dbContext.StockHistories.RemoveRange(oldHistories); + await _dbContext.SaveChangesAsync(cancellationToken); + _logger.LogInformation("{Count} old stock history records deleted.", oldHistories.Count); } - catch (Exception ex) + else { - _logger.LogError(ex, "An error occurred while deleting old stock history records."); - throw; + _logger.LogInformation("No stock history records found to delete."); } } - - public async Task DeleteOldApiCallLogsAsync(CancellationToken cancellationToken) + catch (OperationCanceledException) { - var ninetyDaysAgo = DateTime.UtcNow.AddDays(-90); + _logger.LogInformation("Deletion of old stock history was cancelled."); + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred while deleting old stock history records."); + throw; + } + } - try - { - // Use ExecuteDeleteAsync for efficient bulk deletion (EF Core 7.0+) - int deletedCount = await _dbContext.ApiCallLog - .Where(log => log.CallDate < ninetyDaysAgo) - .ExecuteDeleteAsync(cancellationToken); + public async Task DeleteOldApiCallLogsAsync(CancellationToken cancellationToken) + { + var ninetyDaysAgo = DateTime.UtcNow.AddDays(-90); - if (deletedCount > 0) - { - _logger.LogInformation("{Count} old API call log records deleted.", deletedCount); - } - else - { - _logger.LogInformation("No API call log records found to delete."); - } - } - catch (OperationCanceledException) + try + { + var oldLogs = await _dbContext.ApiCallLog + .Where(log => log.CallDate < ninetyDaysAgo) + .ToListAsync(cancellationToken); + + if (oldLogs.Any()) { - _logger.LogInformation("Deletion of old API call logs was cancelled."); - throw; + _dbContext.ApiCallLog.RemoveRange(oldLogs); + await _dbContext.SaveChangesAsync(cancellationToken); + _logger.LogInformation("{Count} old API call log records deleted.", oldLogs.Count); } - catch (Exception ex) + else { - _logger.LogError(ex, "An error occurred while deleting old API call log records."); - throw; + _logger.LogInformation("No API call log records found to delete."); } } + catch (OperationCanceledException) + { + _logger.LogInformation("Deletion of old API call logs was cancelled."); + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred while deleting old API call log records."); + throw; + } } } diff --git a/XUnitTest/ActionTests/AutoDeleteActionTest.cs b/XUnitTest/ActionTests/AutoDeleteActionTest.cs new file mode 100644 index 0000000..ed7fce9 --- /dev/null +++ b/XUnitTest/ActionTests/AutoDeleteActionTest.cs @@ -0,0 +1,98 @@ +using DatabaseProjectAPI.Actions; +using DatabaseProjectAPI.DataContext; +using DatabaseProjectAPI.Entities; +using DatabaseProjectAPI.Entities.Settings; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Moq; + +namespace XUnitTests.ActionTests; + +public class AutoDeleteActionTests +{ + private DpapiDbContext _dbContext; + private AutoDeleteAction _autoDeleteAction; + private Mock> _loggerMock; + + public AutoDeleteActionTests() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + + _dbContext = new DpapiDbContext(options); + _loggerMock = new Mock>(); + _autoDeleteAction = new AutoDeleteAction(_dbContext, _loggerMock.Object); + } + + [Fact] + public async Task DeleteOldStockHistoryAsync_DeletesOnlyRecordsOlderThan90Days() + { + var now = DateTime.UtcNow; + _dbContext.StockHistories.AddRange(new List + { + new StockHistory { HistoryId = 1, Timestamp = now.AddDays(-91), OpenedValue = 100, ClosedValue = 105, Volume = 1000, StockId = 1, Stock = new Stock { StockId = 1, Symbol = "AAPL" } }, + new StockHistory { HistoryId = 2, Timestamp = now.AddDays(-5), OpenedValue = 110, ClosedValue = 115, Volume = 2000, StockId = 2, Stock = new Stock { StockId = 2, Symbol = "MSFT" } } + }); + + await _dbContext.SaveChangesAsync(); + await _autoDeleteAction.DeleteOldStockHistoryAsync(CancellationToken.None); + + var remaining = await _dbContext.StockHistories.ToListAsync(); + Assert.Single(remaining); + Assert.Equal(2, remaining.First().HistoryId); + } + + [Fact] + public async Task DeleteOldApiCallLogsAsync_DeletesOnlyRecordsOlderThan90Days() + { + var now = DateTime.UtcNow; + _dbContext.ApiCallLog.AddRange(new List + { + new ApiCallLog { Id = 1, Symbol = "AAPL", CallType = "Quote", CallDate = now.AddDays(-91) }, + new ApiCallLog { Id = 2, Symbol = "MSFT", CallType = "Quote", CallDate = now } + }); + + await _dbContext.SaveChangesAsync(); + await _autoDeleteAction.DeleteOldApiCallLogsAsync(CancellationToken.None); + + var remaining = await _dbContext.ApiCallLog.ToListAsync(); + Assert.Single(remaining); + Assert.Equal(2, remaining.First().Id); + } + + [Fact] + public async Task DeleteOldStockHistoryAsync_DoesNotThrow_WhenNoneToDelete() + { + _dbContext.StockHistories.Add(new StockHistory + { + HistoryId = 3, + Timestamp = DateTime.UtcNow, + OpenedValue = 120, + ClosedValue = 125, + Volume = 1500, + StockId = 3, + Stock = new Stock { StockId = 3, Symbol = "GOOG" } + }); + + await _dbContext.SaveChangesAsync(); + var exception = await Record.ExceptionAsync(() => _autoDeleteAction.DeleteOldStockHistoryAsync(CancellationToken.None)); + Assert.Null(exception); + } + + [Fact] + public async Task DeleteOldApiCallLogsAsync_DoesNotThrow_WhenNoneToDelete() + { + _dbContext.ApiCallLog.Add(new ApiCallLog + { + Id = 3, + Symbol = "TSLA", + CallType = "News", + CallDate = DateTime.UtcNow + }); + + await _dbContext.SaveChangesAsync(); + var exception = await Record.ExceptionAsync(() => _autoDeleteAction.DeleteOldApiCallLogsAsync(CancellationToken.None)); + Assert.Null(exception); + } +} diff --git a/XUnitTest/ActionTests/EventActionTests.cs b/XUnitTest/ActionTests/EventActionTests.cs new file mode 100644 index 0000000..0351982 --- /dev/null +++ b/XUnitTest/ActionTests/EventActionTests.cs @@ -0,0 +1,82 @@ +using DatabaseProjectAPI.Actions; +using DatabaseProjectAPI.DataContext; +using Microsoft.EntityFrameworkCore; + +namespace XUnitTests.ActionTests; + +public class EventsActionTests +{ + private readonly DpapiDbContext _dbContext; + private readonly EventsAction _eventsAction; + + public EventsActionTests() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + + _dbContext = new DpapiDbContext(options); + + _dbContext.Events.AddRange(new List + { + new Event + { + Datetime = new DateTime(2024, 10, 1), + CreatedAt = new DateTime(2024, 10, 2), + FederalInterestRate = 5.25m, + UnemploymentRate = 3.8m, + Inflation = 3.2m, + CPI = 302.1m, + }, + new Event + { + Datetime = new DateTime(2024, 11, 1), + CreatedAt = new DateTime(2024, 11, 2), + FederalInterestRate = 5.5m, + UnemploymentRate = 3.6m, + Inflation = 3.5m, + CPI = 305.3m, + } + }); + + _dbContext.SaveChanges(); + + _eventsAction = new EventsAction(_dbContext); + } + + [Fact] + public async Task GetLatestEvent_ReturnsMostRecent() + { + var latest = await _eventsAction.GetLatestEvent(); + Assert.NotNull(latest); + Assert.Equal(new DateTime(2024, 11, 1), latest.Datetime); + } + + [Fact] + public async Task GetFederalInterestRate_ReturnsLatestValue() + { + var rate = await _eventsAction.GetFederalInterestRate(); + Assert.Equal(5.5m, rate); + } + + [Fact] + public async Task GetUnemploymentRate_ReturnsLatestValue() + { + var rate = await _eventsAction.GetUnemploymentRate(); + Assert.Equal(3.6m, rate); + } + + [Fact] + public async Task GetInflation_ReturnsLatestPositiveValue() + { + var inflation = await _eventsAction.GetInflation(); + Assert.Equal(3.5m, inflation); + } + + [Fact] + public async Task GetCPI_ReturnsLatestValue() + { + var cpi = await _eventsAction.GetCPI(); + Assert.Equal(305.3m, cpi); + } +} \ No newline at end of file diff --git a/XUnitTest/ActionTests/MarketNewsActionTests.cs b/XUnitTest/ActionTests/MarketNewsActionTests.cs new file mode 100644 index 0000000..1b356d4 --- /dev/null +++ b/XUnitTest/ActionTests/MarketNewsActionTests.cs @@ -0,0 +1,88 @@ +using DatabaseProjectAPI.Actions; +using DatabaseProjectAPI.DataContext; +using DatabaseProjectAPI.Entities; +using Microsoft.EntityFrameworkCore; + +namespace XUnitTests.ActionTests; + +public class MarketNewsActionTests +{ + private DpapiDbContext GetInMemoryDbContext() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + + var dbContext = new DpapiDbContext(options); + + dbContext.MarketNews.AddRange(new List + { + new MarketNews + { + NewsId = 1, + StockId = 101, + Datetime = new DateTime(2025, 4, 1, 10, 0, 0), + Headline = "Stock 101 rises", + SourceUrl = "http://example.com/1" + }, + new MarketNews + { + NewsId = 2, + StockId = 101, + Datetime = new DateTime(2025, 4, 1, 15, 0, 0), + Headline = "Stock 101 jumps again", + SourceUrl = "http://example.com/2" + }, + new MarketNews + { + NewsId = 3, + StockId = 102, + Datetime = new DateTime(2025, 4, 1), + Headline = "Stock 102 drops", + SourceUrl = "http://example.com/3" + } + }); + + dbContext.SaveChanges(); + return dbContext; + } + + [Fact] + public async Task GetMarketNewsByDateAndStockId_ReturnsMatchingNews() + { + // Arrange + var dbContext = GetInMemoryDbContext(); + var action = new MarketNewsAction(dbContext); + + // Act + var result = await action.GetMarketNewsByDateAndStockId(new DateTime(2025, 4, 1), 101); + + // Assert + Assert.Equal(2, result.Count); + Assert.All(result, r => Assert.Equal(101, r.StockId)); + Assert.True(result[0].Datetime > result[1].Datetime); // check ordering + } + + [Fact] + public async Task GetMarketNewsByDateAndStockId_ReturnsEmpty_WhenNoMatch() + { + var dbContext = GetInMemoryDbContext(); + var action = new MarketNewsAction(dbContext); + + var result = await action.GetMarketNewsByDateAndStockId(new DateTime(2025, 3, 31), 101); + + Assert.Empty(result); + } + + [Fact] + public async Task GetMarketNewsByDateAndStockId_FiltersByStockId() + { + var dbContext = GetInMemoryDbContext(); + var action = new MarketNewsAction(dbContext); + + var result = await action.GetMarketNewsByDateAndStockId(new DateTime(2025, 4, 1), 102); + + Assert.Single(result); + Assert.Equal("Stock 102 drops", result[0].Headline); + } +} diff --git a/XUnitTest/ActionTests/StockActionTests.cs b/XUnitTest/ActionTests/StockActionTests.cs new file mode 100644 index 0000000..583cfaf --- /dev/null +++ b/XUnitTest/ActionTests/StockActionTests.cs @@ -0,0 +1,75 @@ +using DatabaseProjectAPI.Actions; +using DatabaseProjectAPI.DataContext; +using DatabaseProjectAPI.Entities; +using Microsoft.EntityFrameworkCore; + +namespace XUnitTests.ActionTests; + +public class StockActionTests +{ + private DpapiDbContext GetDbContext() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + + var context = new DpapiDbContext(options); + + context.Stocks.AddRange(new List + { + new Stock { StockId = 1, TrackedStockId = 101, Symbol = "AAPL", Name = "Apple" }, + new Stock { StockId = 2, TrackedStockId = 102, Symbol = "GOOGL", Name = "Google" }, + new Stock { StockId = 3, TrackedStockId = 103, Symbol = "AAPL", Name = "Apple Duplicate" } + }); + + context.SaveChanges(); + return context; + } + + [Fact] + public async Task GetStocksById_ReturnsCorrectStock() + { + var context = GetDbContext(); + var action = new StockAction(context); + + var stock = await action.GetStocksById(101); + + Assert.NotNull(stock); + Assert.Equal("AAPL", stock.Symbol); + Assert.Equal("Apple", stock.Name); + } + + [Fact] + public async Task GetAllStocks_ReturnsAllStocks() + { + var context = GetDbContext(); + var action = new StockAction(context); + + var stocks = await action.GetAllStocks(); + + Assert.Equal(3, stocks.Count); + } + + [Fact] + public async Task GetStocksBySymbol_ReturnsMatchingStocks() + { + var context = GetDbContext(); + var action = new StockAction(context); + + var stocks = await action.GetStocksBySymbol("AAPL"); + + Assert.Equal(2, stocks.Count); + Assert.All(stocks, s => Assert.Equal("AAPL", s.Symbol)); + } + + [Fact] + public async Task GetStocksBySymbol_ReturnsEmptyList_IfNoMatch() + { + var context = GetDbContext(); + var action = new StockAction(context); + + var stocks = await action.GetStocksBySymbol("MSFT"); + + Assert.Empty(stocks); + } +} diff --git a/XUnitTest/ActionTests/StockHistoryActionTests.cs b/XUnitTest/ActionTests/StockHistoryActionTests.cs new file mode 100644 index 0000000..afd070f --- /dev/null +++ b/XUnitTest/ActionTests/StockHistoryActionTests.cs @@ -0,0 +1,68 @@ +using DatabaseProjectAPI.Actions; +using DatabaseProjectAPI.DataContext; +using DatabaseProjectAPI.Entities; +using Microsoft.EntityFrameworkCore; + +namespace XUnitTests.ActionTests; + +public class StockHistoryActionTests +{ + private DpapiDbContext CreateDbContext() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var context = new DpapiDbContext(options); + + var aaplStock = new Stock { StockId = 1, Symbol = "AAPL", Name = "Apple Inc." }; + var googlStock = new Stock { StockId = 2, Symbol = "GOOGL", Name = "Alphabet Inc." }; + + context.Stocks.AddRange(aaplStock, googlStock); + context.StockHistories.AddRange( + new StockHistory { HistoryId = 1, Stock = aaplStock, Timestamp = new DateTime(2025, 3, 15), OpenedValue = 150, ClosedValue = 155, Volume = 100000 }, + new StockHistory { HistoryId = 2, Stock = aaplStock, Timestamp = new DateTime(2025, 3, 10), OpenedValue = 148, ClosedValue = 153, Volume = 90000 }, + new StockHistory { HistoryId = 3, Stock = googlStock, Timestamp = new DateTime(2025, 3, 15), OpenedValue = 2800, ClosedValue = 2820, Volume = 110000 } + ); + + context.SaveChanges(); + return context; + } + + [Fact] + public void GetStockHistory_ReturnsAllForSymbol() + { + var dbContext = CreateDbContext(); + var action = new StockHistoryAction(dbContext); + + var result = action.GetStockHistory("AAPL"); + + Assert.Equal(2, result.Count); + Assert.All(result, sh => Assert.Equal("AAPL", sh.Stock.Symbol)); + } + + [Fact] + public void GetStockHistory_WithDateRange_FiltersCorrectly() + { + var dbContext = CreateDbContext(); + var action = new StockHistoryAction(dbContext); + var from = new DateTime(2025, 3, 12); + var to = new DateTime(2025, 3, 16); + + var result = action.GetStockHistory("AAPL", from, to); + + Assert.Single(result); + Assert.Equal(new DateTime(2025, 3, 15), result.First().Timestamp); + } + + [Fact] + public void GetStockHistory_ReturnsEmpty_WhenNoMatch() + { + var dbContext = CreateDbContext(); + var action = new StockHistoryAction(dbContext); + + var result = action.GetStockHistory("MSFT"); + + Assert.Empty(result); + } +} diff --git a/XUnitTest/ActionTests/TrackedStockActionTests.cs b/XUnitTest/ActionTests/TrackedStockActionTests.cs new file mode 100644 index 0000000..0476c17 --- /dev/null +++ b/XUnitTest/ActionTests/TrackedStockActionTests.cs @@ -0,0 +1,70 @@ +using DatabaseProjectAPI.Actions; +using DatabaseProjectAPI.DataContext; +using DatabaseProjectAPI.Entities; +using Microsoft.EntityFrameworkCore; + +namespace XUnitTests.ActionTests; + +public class TrackedStockActionTests +{ + private DpapiDbContext _dbContext; + private TrackedStockAction _trackedStockAction; + + public TrackedStockActionTests() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + + _dbContext = new DpapiDbContext(options); + _dbContext.TrackedStocks.AddRange(new List + { + new TrackedStock { Id = 1, Symbol = "AAPL", StockName = "Apple Inc." }, + new TrackedStock { Id = 2, Symbol = "AMZN", StockName = "Amazon Inc." }, + new TrackedStock { Id = 3, Symbol = "GOOGL", StockName = "Alphabet Inc." } + }); + _dbContext.SaveChanges(); + + _trackedStockAction = new TrackedStockAction(_dbContext); + } + + [Fact] + public void GetTrackedStocks_ReturnsAll_WhenNoSymbolProvided() + { + var result = _trackedStockAction.GetTrackedStocks(); + Assert.Equal(3, result.Count); + } + + [Fact] + public void GetTrackedStocks_ReturnsFilteredList_WhenSymbolProvided() + { + var result = _trackedStockAction.GetTrackedStocks("A"); + + Assert.Equal(2, result.Count); // AAPL and AMZN + Assert.All(result, s => Assert.StartsWith("A", s.Symbol)); + } + + [Fact] + public void AddTrackedStock_AddsNewStock_WhenNotExists() + { + var newStock = new TrackedStock { Symbol = "MSFT", StockName = "Microsoft" }; + + _trackedStockAction.AddTrackedStock(newStock); + + var all = _trackedStockAction.GetTrackedStocks(); + Assert.Equal(4, all.Count); + Assert.Contains(all, s => s.Symbol == "MSFT"); + } + + [Fact] + public void AddTrackedStock_DoesNotAddDuplicate() + { + var duplicateStock = new TrackedStock { Symbol = "AAPL", StockName = "Apple Duplicate" }; + + _trackedStockAction.AddTrackedStock(duplicateStock); + + var result = _trackedStockAction.GetTrackedStocks("AAPL"); + Assert.Single(result); + Assert.Equal("Apple Inc.", result[0].StockName); + } +} diff --git a/XUnitTest/AutoDeleteActionTest.cs b/XUnitTest/AutoDeleteActionTest.cs deleted file mode 100644 index 6aa2e2f..0000000 --- a/XUnitTest/AutoDeleteActionTest.cs +++ /dev/null @@ -1,42 +0,0 @@ -using DatabaseProjectAPI.Actions; -using DatabaseProjectAPI.DataContext; -using DatabaseProjectAPI.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Moq; - -namespace XUnitTest; - -public class AutoDeleteActionTest -{ - private List MockStockHistoryList() - { - return new List - { - new StockHistory {HistoryId=1, Timestamp = DateTime.UtcNow.AddDays(-95)}, - new StockHistory {HistoryId =2, Timestamp = DateTime.UtcNow.AddDays(-100)} - - }; - } - - [Fact] - public async Task DeleteOldStockHistoryTest_ShouldDelete() - { - var mockDbContext = new Mock(); - var mockLogger = new Mock>(); - var cancellationToken = new CancellationToken(); - var service = new AutoDeleteAction(mockDbContext.Object, mockLogger.Object); - - var stockHistoryList = MockStockHistoryList(); - var mockStockHistoryDbSet = new Mock>(); - - mockDbContext.Setup(db => db.StockHistories).Returns(mockStockHistoryDbSet.Object); - mockStockHistoryDbSet.Setup(db => db.ExecuteDeleteAsync(cancellationToken)).ReturnsAsync(stockHistoryList.Count); - - var result = service.DeleteOldStockHistoryAsync(cancellationToken); - - Assert.NotNull(result); - - mockLogger.Verify(x => x.LogInformation("{Count} old stock history records deleted.", stockHistoryList.Count), Times.Once); - } -} \ No newline at end of file diff --git a/XUnitTest/ServiceTests/AlphaVantageServiceTests.cs b/XUnitTest/ServiceTests/AlphaVantageServiceTests.cs new file mode 100644 index 0000000..bf5b3e4 --- /dev/null +++ b/XUnitTest/ServiceTests/AlphaVantageServiceTests.cs @@ -0,0 +1,111 @@ +using DatabaseProjectAPI.Services; +using KubsConnect.Settings; +using Microsoft.Extensions.Logging; +using Moq; +using Moq.Protected; +using Newtonsoft.Json; +using System.Net; +using System.Text; + +namespace XUnitTests.ServiceTests +{ + public class AlphaVantageServiceTests + { + private readonly Mock _handlerMock; + private readonly Mock> _loggerMock; + private readonly HttpClient _httpClient; + private readonly AlphaVantageService _service; + + public AlphaVantageServiceTests() + { + _handlerMock = new Mock(); + _loggerMock = new Mock>(); + + _httpClient = new HttpClient(_handlerMock.Object); + var settings = new AlphaVantageSettings { ApiKey = "demo" }; + + _service = new AlphaVantageService(_httpClient, settings, _loggerMock.Object); + } + + [Fact] + public async Task GetStockQuoteAsync_ReturnsValidStockQuote() + { + var today = DateTime.UtcNow.Date; + + var responseObject = new Dictionary + { + ["Global Quote"] = new Dictionary + { + ["01. symbol"] = "AAPL", + ["02. open"] = "170.5", + ["03. high"] = "173.0", + ["04. low"] = "169.5", + ["05. price"] = "172.1", + ["06. volume"] = "5000000", + ["07. latest trading day"] = today.ToString("yyyy-MM-dd"), + ["08. previous close"] = "171.0", + ["10. change percent"] = "0.64%" + } + }; + + var responseContent = JsonConvert.SerializeObject(responseObject); + + _handlerMock.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(responseContent, Encoding.UTF8, "application/json") + }); + + var result = await _service.GetStockQuoteAsync("AAPL"); + + Assert.NotNull(result); + Assert.Equal("AAPL", result.Symbol); + Assert.Equal(170.5m, result.Open); + Assert.Equal(172.1m, result.Price); + Assert.Equal(5000000, result.Volume); + Assert.Equal(today, result.LatestTradingDay.Date); + } + + + [Fact] + public async Task GetStockQuoteAsync_ThrowsOnRateLimit() + { + var rateLimitResponse = JsonConvert.SerializeObject(new { Note = "Rate limit exceeded" }); + + _handlerMock.Protected() + .Setup>("SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(rateLimitResponse) + }); + + await Assert.ThrowsAsync(() => _service.GetStockQuoteAsync("AAPL")); + } + + [Fact] + public async Task GetStockQuoteAsync_ThrowsOnErrorMessage() + { + var errorResponse = JsonConvert.SerializeObject(new { ErrorMessage = "Invalid API call" }); + + _handlerMock.Protected() + .Setup>("SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(errorResponse) + }); + + await Assert.ThrowsAsync(() => _service.GetStockQuoteAsync("INVALID")); + } + } +} diff --git a/XUnitTest/ServiceTests/DataCleanupBackgroundServiceTests.cs b/XUnitTest/ServiceTests/DataCleanupBackgroundServiceTests.cs new file mode 100644 index 0000000..cc35f21 --- /dev/null +++ b/XUnitTest/ServiceTests/DataCleanupBackgroundServiceTests.cs @@ -0,0 +1,44 @@ +using DatabaseProjectAPI.Actions; +using DatabaseProjectAPI.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Moq; + +namespace XUnitTests.ServiceTests +{ + public class DataCleanupBackgroundServiceTests + { + [Fact] + public async Task ExecuteAsync_CallsAutoDeleteMethodsOnce() + { + // Arrange + var autoDeleteServiceMock = new Mock(); + autoDeleteServiceMock.Setup(x => x.DeleteOldStockHistoryAsync(It.IsAny())).Returns(Task.CompletedTask); + autoDeleteServiceMock.Setup(x => x.DeleteOldApiCallLogsAsync(It.IsAny())).Returns(Task.CompletedTask); + + var serviceProviderMock = new Mock(); + var serviceScopeMock = new Mock(); + var scopeFactoryMock = new Mock(); + + serviceScopeMock.Setup(x => x.ServiceProvider).Returns(serviceProviderMock.Object); + scopeFactoryMock.Setup(x => x.CreateScope()).Returns(serviceScopeMock.Object); + + serviceProviderMock.Setup(x => x.GetService(typeof(IServiceScopeFactory))).Returns(scopeFactoryMock.Object); + serviceProviderMock.Setup(x => x.GetRequiredService()).Returns(autoDeleteServiceMock.Object); + + var loggerMock = new Mock>(); + + var service = new DataCleanupBackgroundService(serviceProviderMock.Object, loggerMock.Object); + + using var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromSeconds(1)); // Short-circuit the while loop for test + + // Act + await service.StartAsync(cts.Token); + + // Assert + autoDeleteServiceMock.Verify(x => x.DeleteOldStockHistoryAsync(It.IsAny()), Times.AtLeastOnce); + autoDeleteServiceMock.Verify(x => x.DeleteOldApiCallLogsAsync(It.IsAny()), Times.AtLeastOnce); + } + } +} diff --git a/XUnitTest/ServiceTests/EventsBackgroundServiceTests.cs b/XUnitTest/ServiceTests/EventsBackgroundServiceTests.cs new file mode 100644 index 0000000..0ad7159 --- /dev/null +++ b/XUnitTest/ServiceTests/EventsBackgroundServiceTests.cs @@ -0,0 +1,71 @@ +using DatabaseProjectAPI.DataContext; +using DatabaseProjectAPI.Entities; +using DatabaseProjectAPI.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Moq; + +namespace XUnitTests.ServiceTests; + +public class EventsBackgroundServiceTests +{ + private readonly Mock _serviceProviderMock; + private readonly Mock _serviceScopeMock; + private readonly Mock _scopeFactoryMock; + private readonly Mock> _loggerMock; + private readonly Mock _alphaVantageServiceMock; + private readonly DpapiDbContext _dbContext; + private readonly EventsBackgroundService _service; + + public EventsBackgroundServiceTests() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + + _dbContext = new DpapiDbContext(options); + _loggerMock = new Mock>(); + _alphaVantageServiceMock = new Mock(); + + _serviceProviderMock = new Mock(); + _serviceScopeMock = new Mock(); + _scopeFactoryMock = new Mock(); + + _serviceScopeMock.Setup(s => s.ServiceProvider).Returns(_serviceProviderMock.Object); + _scopeFactoryMock.Setup(f => f.CreateScope()).Returns(_serviceScopeMock.Object); + + _serviceProviderMock.Setup(x => x.GetService(typeof(IServiceScopeFactory))) + .Returns(_scopeFactoryMock.Object); + _serviceProviderMock.Setup(x => x.GetService(typeof(DpapiDbContext))) + .Returns(_dbContext); + _serviceProviderMock.Setup(x => x.GetService(typeof(IAlphaVantageService))) + .Returns(_alphaVantageServiceMock.Object); + + _service = new EventsBackgroundService(_serviceProviderMock.Object, _loggerMock.Object); + } + + [Fact] + public async Task ExecuteAsync_CallsAllFetchMethods() + { + // Arrange mock data + var now = DateTime.UtcNow; + _alphaVantageServiceMock.Setup(a => a.GetInflationAsync()).ReturnsAsync( + new Inflation { Data = new List { new InflationDataPoint { Date = now.ToString("yyyy-MM-dd"), Value = "3.5" } } }); + _alphaVantageServiceMock.Setup(a => a.GetFederalInterestRateAsync()).ReturnsAsync( + new FederalInterestRate { Data = new List { new FederalInterestRateDataPoint { Date = now.ToString("yyyy-MM-dd"), Value = "4.5" } } }); + _alphaVantageServiceMock.Setup(a => a.GetUnemploymentRateAsync()).ReturnsAsync( + new UnemploymentRate { Data = new List { new UnemploymentRateDataPoint { Date = now.ToString("yyyy-MM-dd"), Value = "5.1" } } }); + _alphaVantageServiceMock.Setup(a => a.GetCPIdataAsync()).ReturnsAsync( + new CPIdata { Data = new List { new CpiDataPoint { Date = now.ToString("yyyy-MM-dd"), Value = "260.7" } } }); + + using var cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.CancelAfter(200); // run briefly + + // Act + await _service.StartAsync(cancellationTokenSource.Token); + + // Assert + Assert.True(await _dbContext.Events.AnyAsync()); + } +} diff --git a/XUnitTest/ServiceTests/FinnhubServiceTests.cs b/XUnitTest/ServiceTests/FinnhubServiceTests.cs new file mode 100644 index 0000000..d23f408 --- /dev/null +++ b/XUnitTest/ServiceTests/FinnhubServiceTests.cs @@ -0,0 +1,88 @@ +using DatabaseProjectAPI.Entities; +using DatabaseProjectAPI.Services; +using KubsConnect.Settings; +using Moq; +using Moq.Protected; +using Newtonsoft.Json; +using System.Net; +using System.Text; + +namespace XUnitTests.ServiceTests +{ + public class FinnhubServiceTests + { + private readonly Mock _handlerMock; + private readonly HttpClient _httpClient; + private readonly FinnhubService _service; + + public FinnhubServiceTests() + { + _handlerMock = new Mock(); + _httpClient = new HttpClient(_handlerMock.Object); + + var settings = new FinnhubSettings + { + ApiKey = "test-api-key" + }; + + _service = new FinnhubService(_httpClient, settings); + } + + [Fact] + public async Task GetStockDataAsync_ReturnsParsedJObject() + { + var responseContent = new + { + c = new[] { 150.1, 151.2 }, + t = new[] { 1617753600, 1617840000 } + }; + + var json = JsonConvert.SerializeObject(responseContent); + + _handlerMock.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(json, Encoding.UTF8, "application/json") + }); + + var result = await _service.GetStockDataAsync("AAPL", DateTime.UtcNow.AddDays(-2), DateTime.UtcNow); + + Assert.NotNull(result); + Assert.True(result.ContainsKey("c")); + } + + [Fact] + public async Task MarkStatusAsync_ReturnsDeserializedStatus() + { + var marketStatus = new FinnhubMarketStatus + { + isOpen = false, + exchange = "US" + }; + + var json = JsonConvert.SerializeObject(marketStatus); + + _handlerMock.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(json, Encoding.UTF8, "application/json") + }); + + var result = await _service.MarkStatusAsync(); + + Assert.NotNull(result); + Assert.False(result.isOpen); + Assert.Equal("US", result.exchange); + } + } +} \ No newline at end of file diff --git a/XUnitTest/ServiceTests/NewsAPIServiceTests.cs b/XUnitTest/ServiceTests/NewsAPIServiceTests.cs new file mode 100644 index 0000000..4a96f31 --- /dev/null +++ b/XUnitTest/ServiceTests/NewsAPIServiceTests.cs @@ -0,0 +1,73 @@ +using DatabaseProjectAPI.Entities; +using DatabaseProjectAPI.Services; +using KubsConnect.Settings; +using Moq; +using Moq.Protected; +using Newtonsoft.Json; +using System.Net; +using System.Text; + +namespace XUnitTests.ServiceTests +{ + public class NewsAPIServiceTests + { + private readonly Mock _handlerMock; + private readonly HttpClient _httpClient; + private readonly NewsAPIService _service; + + public NewsAPIServiceTests() + { + _handlerMock = new Mock(MockBehavior.Strict); + _httpClient = new HttpClient(_handlerMock.Object); + var settings = new NewsSettings { ApiKey = "demo" }; + + _service = new NewsAPIService(_httpClient, settings); + } + + [Fact] + public async Task GetNewsDataAsync_ReturnsArticles() + { + var articles = new List
+ { + new Article { Title = "Test Article", Url = "http://example.com", PublishedAt = DateTime.UtcNow } + }; + + var response = new NewsApiResponse { Articles = articles }; + var json = JsonConvert.SerializeObject(response); + + _handlerMock.Protected() + .Setup>("SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(json, Encoding.UTF8, "application/json") + }); + + var result = await _service.GetNewsDataAsync("apple", DateTime.UtcNow.AddDays(-1), DateTime.UtcNow); + + Assert.Single(result); + Assert.Equal("Test Article", result[0].Title); + } + + [Fact] + public async Task GetNewsDataAsync_ThrowsOnError() + { + _handlerMock.Protected() + .Setup>("SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.BadRequest, + Content = new StringContent("Invalid request") + }); + + var ex = await Assert.ThrowsAsync(() => + _service.GetNewsDataAsync("apple", DateTime.UtcNow.AddDays(-1), DateTime.UtcNow)); + + Assert.Contains("status code", ex.Message); + } + } +} diff --git a/XUnitTest/ServiceTests/NewsBackgroundServiceTests.cs b/XUnitTest/ServiceTests/NewsBackgroundServiceTests.cs new file mode 100644 index 0000000..2ca7441 --- /dev/null +++ b/XUnitTest/ServiceTests/NewsBackgroundServiceTests.cs @@ -0,0 +1,63 @@ +using DatabaseProjectAPI.DataContext; +using DatabaseProjectAPI.Entities; +using DatabaseProjectAPI.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Moq; + +namespace XUnitTests.ServiceTests; + +public class NewsBackgroundServiceTests +{ + [Fact] + public async Task FetchAndSaveNewsAsync_SavesNews_WhenValidResponse() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + await using var dbContext = new DpapiDbContext(options); + + var stock = new Stock { StockId = 1, Symbol = "AAPL", Name = "Apple Inc." }; + var trackedStock = new TrackedStock { Id = 1, Symbol = "AAPL", StockName = "Apple Inc." }; + + dbContext.Stocks.Add(stock); + dbContext.TrackedStocks.Add(trackedStock); + await dbContext.SaveChangesAsync(); + + var mockNewsService = new Mock(); + mockNewsService.Setup(x => x.GetNewsDataAsync("Apple Inc.", It.IsAny(), It.IsAny())) + .ReturnsAsync(new List
+ { + new Article + { + Title = "Apple releases new iPhone", + Url = "https://example.com/apple-news", + PublishedAt = DateTime.UtcNow + } + }); + + var serviceProviderMock = new Mock(); + var scopeFactoryMock = new Mock(); + var scopeMock = new Mock(); + var scopedServiceProviderMock = new Mock(); + + scopedServiceProviderMock.Setup(x => x.GetService(typeof(DpapiDbContext))).Returns(dbContext); + scopeMock.Setup(x => x.ServiceProvider).Returns(scopedServiceProviderMock.Object); + scopeFactoryMock.Setup(x => x.CreateScope()).Returns(scopeMock.Object); + serviceProviderMock.Setup(x => x.GetService(typeof(IServiceScopeFactory))).Returns(scopeFactoryMock.Object); + + var loggerMock = new Mock>(); + var service = new NewsBackgroundService(serviceProviderMock.Object, loggerMock.Object, mockNewsService.Object); + + // Act + await service.FetchAndSaveNewsAsync(CancellationToken.None); + + // Assert + Assert.Single(dbContext.MarketNews); + var savedNews = dbContext.MarketNews.First(); + Assert.Equal("Apple releases new iPhone", savedNews.Headline); + } +} diff --git a/XUnitTest/ServiceTests/StockQuoteBackgroundServiceTests.cs b/XUnitTest/ServiceTests/StockQuoteBackgroundServiceTests.cs new file mode 100644 index 0000000..25a92e7 --- /dev/null +++ b/XUnitTest/ServiceTests/StockQuoteBackgroundServiceTests.cs @@ -0,0 +1,76 @@ +using DatabaseProjectAPI.Actions; +using DatabaseProjectAPI.DataContext; +using DatabaseProjectAPI.Entities; +using DatabaseProjectAPI.Helpers; +using DatabaseProjectAPI.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Moq; + +namespace XUnitTests.ServiceTests +{ + public class StockQuoteBackgroundServiceTests + { + private readonly Mock> _loggerMock = new(); + private readonly Mock _finnhubServiceMock = new(); + private readonly Mock _alphaVantageServiceMock = new(); + private readonly Mock _apiRequestLoggerMock = new(); + private readonly Mock _autoDeleteServiceMock = new(); + private readonly DbContextOptions _dbContextOptions; + + public StockQuoteBackgroundServiceTests() + { + _dbContextOptions = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + } + + [Fact] + public async Task ExecuteAsync_FetchesAndSavesStockData_WhenMarketIsClosed() + { + // Arrange + var dbContext = new DpapiDbContext(_dbContextOptions); + var trackedStock = new TrackedStock { Id = 1, Symbol = "AAPL", StockName = "Apple Inc." }; + dbContext.TrackedStocks.Add(trackedStock); + await dbContext.SaveChangesAsync(); + + _finnhubServiceMock.Setup(s => s.MarkStatusAsync()).ReturnsAsync(new FinnhubMarketStatus { isOpen = false }); + _apiRequestLoggerMock.Setup(s => s.HasMadeApiCallTodayAsync("MarketClose", trackedStock.Symbol, It.IsAny())).ReturnsAsync(false); + _alphaVantageServiceMock.Setup(s => s.GetStockQuoteAsync(trackedStock.Symbol)).ReturnsAsync(new StockQuote + { + Symbol = trackedStock.Symbol, + Open = 150, + Price = 155, + Volume = 100000, + LatestTradingDay = DateTime.UtcNow.Date + }); + + var services = new ServiceCollection(); + services.AddSingleton(_ => dbContext); + services.AddSingleton(_apiRequestLoggerMock.Object); + services.AddSingleton(_autoDeleteServiceMock.Object); + services.AddSingleton(_alphaVantageServiceMock.Object); + var serviceProvider = services.BuildServiceProvider(); + + var backgroundService = new StockQuoteBackgroundService(serviceProvider, _loggerMock.Object, _finnhubServiceMock.Object); + + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + + // Act + var runTask = backgroundService.StartAsync(cts.Token); + await Task.Delay(3000); // simulate execution time + cts.Cancel(); + await runTask; + + // Assert + var savedStock = await dbContext.Stocks.FirstOrDefaultAsync(); + Assert.NotNull(savedStock); + Assert.Equal(150, savedStock.OpenValue); + Assert.Equal(155, savedStock.ClosingValue); + var history = await dbContext.StockHistories.FirstOrDefaultAsync(); + Assert.NotNull(history); + Assert.Equal(150, history.OpenedValue); + } + } +} \ No newline at end of file diff --git a/XUnitTest/XUnitTest.csproj b/XUnitTest/XUnitTest.csproj index e7e8a43..a54c07f 100644 --- a/XUnitTest/XUnitTest.csproj +++ b/XUnitTest/XUnitTest.csproj @@ -11,7 +11,9 @@ + + From 194ea0e52abd1d4a69f26e3867051cc31c85d704 Mon Sep 17 00:00:00 2001 From: Ryan Dudley Date: Mon, 7 Apr 2025 18:12:32 -0400 Subject: [PATCH 3/6] final xunit --- .../Services/StockQuoteBackgroundService.cs | 2 +- XUnitTest/ActionTests/AutoDeleteActionTest.cs | 9 - XUnitTest/ActionTests/EventActionTests.cs | 7 +- .../ActionTests/MarketNewsActionTests.cs | 8 +- XUnitTest/ActionTests/StockActionTests.cs | 8 +- .../ActionTests/StockHistoryActionTests.cs | 8 +- .../ActionTests/TrackedStockActionTests.cs | 8 +- XUnitTest/GlobalUsingsXUnit.cs | 15 ++ .../ServiceTests/AlphaVantageServiceTests.cs | 189 +++++++++--------- .../DataCleanupBackgroundServiceTests.cs | 56 +++--- .../EventsBackgroundServiceTests.cs | 72 +++---- XUnitTest/ServiceTests/FinnhubServiceTests.cs | 127 ++++++------ XUnitTest/ServiceTests/NewsAPIServiceTests.cs | 108 +++++----- .../NewsBackgroundServiceTests.cs | 11 +- .../StockQuoteBackgroundServiceTests.cs | 113 +++++------ 15 files changed, 325 insertions(+), 416 deletions(-) create mode 100644 XUnitTest/GlobalUsingsXUnit.cs diff --git a/DatabaseProjectAPI/Services/StockQuoteBackgroundService.cs b/DatabaseProjectAPI/Services/StockQuoteBackgroundService.cs index 55505d1..2581dfe 100644 --- a/DatabaseProjectAPI/Services/StockQuoteBackgroundService.cs +++ b/DatabaseProjectAPI/Services/StockQuoteBackgroundService.cs @@ -64,7 +64,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); } } - private async Task FetchAndSaveStockDataAsync(DpapiDbContext dbContext, IApiRequestLogger apiRequestLogger, string callType, TrackedStock trackedStock, CancellationToken cancellationToken) + public async Task FetchAndSaveStockDataAsync(DpapiDbContext dbContext, IApiRequestLogger apiRequestLogger, string callType, TrackedStock trackedStock, CancellationToken cancellationToken) { var symbol = trackedStock.Symbol; _logger.LogInformation("FetchAndSaveStockDataAsync started for symbol {Symbol} with call type {CallType}", symbol, callType); diff --git a/XUnitTest/ActionTests/AutoDeleteActionTest.cs b/XUnitTest/ActionTests/AutoDeleteActionTest.cs index ed7fce9..840b129 100644 --- a/XUnitTest/ActionTests/AutoDeleteActionTest.cs +++ b/XUnitTest/ActionTests/AutoDeleteActionTest.cs @@ -1,13 +1,4 @@ -using DatabaseProjectAPI.Actions; -using DatabaseProjectAPI.DataContext; -using DatabaseProjectAPI.Entities; -using DatabaseProjectAPI.Entities.Settings; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Moq; - namespace XUnitTests.ActionTests; - public class AutoDeleteActionTests { private DpapiDbContext _dbContext; diff --git a/XUnitTest/ActionTests/EventActionTests.cs b/XUnitTest/ActionTests/EventActionTests.cs index 0351982..10fa8ce 100644 --- a/XUnitTest/ActionTests/EventActionTests.cs +++ b/XUnitTest/ActionTests/EventActionTests.cs @@ -1,9 +1,4 @@ -using DatabaseProjectAPI.Actions; -using DatabaseProjectAPI.DataContext; -using Microsoft.EntityFrameworkCore; - -namespace XUnitTests.ActionTests; - +namespace XUnitTests.ActionTests; public class EventsActionTests { private readonly DpapiDbContext _dbContext; diff --git a/XUnitTest/ActionTests/MarketNewsActionTests.cs b/XUnitTest/ActionTests/MarketNewsActionTests.cs index 1b356d4..acff781 100644 --- a/XUnitTest/ActionTests/MarketNewsActionTests.cs +++ b/XUnitTest/ActionTests/MarketNewsActionTests.cs @@ -1,10 +1,4 @@ -using DatabaseProjectAPI.Actions; -using DatabaseProjectAPI.DataContext; -using DatabaseProjectAPI.Entities; -using Microsoft.EntityFrameworkCore; - -namespace XUnitTests.ActionTests; - +namespace XUnitTests.ActionTests; public class MarketNewsActionTests { private DpapiDbContext GetInMemoryDbContext() diff --git a/XUnitTest/ActionTests/StockActionTests.cs b/XUnitTest/ActionTests/StockActionTests.cs index 583cfaf..02d87ac 100644 --- a/XUnitTest/ActionTests/StockActionTests.cs +++ b/XUnitTest/ActionTests/StockActionTests.cs @@ -1,10 +1,4 @@ -using DatabaseProjectAPI.Actions; -using DatabaseProjectAPI.DataContext; -using DatabaseProjectAPI.Entities; -using Microsoft.EntityFrameworkCore; - -namespace XUnitTests.ActionTests; - +namespace XUnitTests.ActionTests; public class StockActionTests { private DpapiDbContext GetDbContext() diff --git a/XUnitTest/ActionTests/StockHistoryActionTests.cs b/XUnitTest/ActionTests/StockHistoryActionTests.cs index afd070f..bce5be9 100644 --- a/XUnitTest/ActionTests/StockHistoryActionTests.cs +++ b/XUnitTest/ActionTests/StockHistoryActionTests.cs @@ -1,10 +1,4 @@ -using DatabaseProjectAPI.Actions; -using DatabaseProjectAPI.DataContext; -using DatabaseProjectAPI.Entities; -using Microsoft.EntityFrameworkCore; - -namespace XUnitTests.ActionTests; - +namespace XUnitTests.ActionTests; public class StockHistoryActionTests { private DpapiDbContext CreateDbContext() diff --git a/XUnitTest/ActionTests/TrackedStockActionTests.cs b/XUnitTest/ActionTests/TrackedStockActionTests.cs index 0476c17..749956e 100644 --- a/XUnitTest/ActionTests/TrackedStockActionTests.cs +++ b/XUnitTest/ActionTests/TrackedStockActionTests.cs @@ -1,10 +1,4 @@ -using DatabaseProjectAPI.Actions; -using DatabaseProjectAPI.DataContext; -using DatabaseProjectAPI.Entities; -using Microsoft.EntityFrameworkCore; - -namespace XUnitTests.ActionTests; - +namespace XUnitTests.ActionTests; public class TrackedStockActionTests { private DpapiDbContext _dbContext; diff --git a/XUnitTest/GlobalUsingsXUnit.cs b/XUnitTest/GlobalUsingsXUnit.cs new file mode 100644 index 0000000..188d51a --- /dev/null +++ b/XUnitTest/GlobalUsingsXUnit.cs @@ -0,0 +1,15 @@ +global using DatabaseProjectAPI.DataContext; +global using DatabaseProjectAPI.Entities; +global using DatabaseProjectAPI.Services; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Logging; +global using Moq; +global using DatabaseProjectAPI.Helpers; +global using DatabaseProjectAPI.Actions; +global using Moq.Protected; +global using Newtonsoft.Json; +global using System.Net; +global using System.Text; +global using KubsConnect.Settings; +global using DatabaseProjectAPI.Entities.Settings; \ No newline at end of file diff --git a/XUnitTest/ServiceTests/AlphaVantageServiceTests.cs b/XUnitTest/ServiceTests/AlphaVantageServiceTests.cs index bf5b3e4..0f58cf9 100644 --- a/XUnitTest/ServiceTests/AlphaVantageServiceTests.cs +++ b/XUnitTest/ServiceTests/AlphaVantageServiceTests.cs @@ -1,111 +1,100 @@ -using DatabaseProjectAPI.Services; -using KubsConnect.Settings; -using Microsoft.Extensions.Logging; -using Moq; -using Moq.Protected; -using Newtonsoft.Json; -using System.Net; -using System.Text; - -namespace XUnitTests.ServiceTests +namespace XUnitTests.ServiceTests; +public class AlphaVantageServiceTests { - public class AlphaVantageServiceTests + private readonly Mock _handlerMock; + private readonly Mock> _loggerMock; + private readonly HttpClient _httpClient; + private readonly AlphaVantageService _service; + + public AlphaVantageServiceTests() { - private readonly Mock _handlerMock; - private readonly Mock> _loggerMock; - private readonly HttpClient _httpClient; - private readonly AlphaVantageService _service; + _handlerMock = new Mock(); + _loggerMock = new Mock>(); - public AlphaVantageServiceTests() - { - _handlerMock = new Mock(); - _loggerMock = new Mock>(); + _httpClient = new HttpClient(_handlerMock.Object); + var settings = new AlphaVantageSettings { ApiKey = "demo" }; - _httpClient = new HttpClient(_handlerMock.Object); - var settings = new AlphaVantageSettings { ApiKey = "demo" }; + _service = new AlphaVantageService(_httpClient, settings, _loggerMock.Object); + } - _service = new AlphaVantageService(_httpClient, settings, _loggerMock.Object); - } + [Fact] + public async Task GetStockQuoteAsync_ReturnsValidStockQuote() + { + var today = DateTime.UtcNow.Date; - [Fact] - public async Task GetStockQuoteAsync_ReturnsValidStockQuote() + var responseObject = new Dictionary { - var today = DateTime.UtcNow.Date; + ["Global Quote"] = new Dictionary + { + ["01. symbol"] = "AAPL", + ["02. open"] = "170.5", + ["03. high"] = "173.0", + ["04. low"] = "169.5", + ["05. price"] = "172.1", + ["06. volume"] = "5000000", + ["07. latest trading day"] = today.ToString("yyyy-MM-dd"), + ["08. previous close"] = "171.0", + ["10. change percent"] = "0.64%" + } + }; + + var responseContent = JsonConvert.SerializeObject(responseObject); + + _handlerMock.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(responseContent, Encoding.UTF8, "application/json") + }); + + var result = await _service.GetStockQuoteAsync("AAPL"); + + Assert.NotNull(result); + Assert.Equal("AAPL", result.Symbol); + Assert.Equal(170.5m, result.Open); + Assert.Equal(172.1m, result.Price); + Assert.Equal(5000000, result.Volume); + Assert.Equal(today, result.LatestTradingDay.Date); + } + + + [Fact] + public async Task GetStockQuoteAsync_ThrowsOnRateLimit() + { + var rateLimitResponse = JsonConvert.SerializeObject(new { Note = "Rate limit exceeded" }); - var responseObject = new Dictionary + _handlerMock.Protected() + .Setup>("SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage { - ["Global Quote"] = new Dictionary - { - ["01. symbol"] = "AAPL", - ["02. open"] = "170.5", - ["03. high"] = "173.0", - ["04. low"] = "169.5", - ["05. price"] = "172.1", - ["06. volume"] = "5000000", - ["07. latest trading day"] = today.ToString("yyyy-MM-dd"), - ["08. previous close"] = "171.0", - ["10. change percent"] = "0.64%" - } - }; - - var responseContent = JsonConvert.SerializeObject(responseObject); - - _handlerMock.Protected() - .Setup>( - "SendAsync", - ItExpr.IsAny(), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(responseContent, Encoding.UTF8, "application/json") - }); - - var result = await _service.GetStockQuoteAsync("AAPL"); - - Assert.NotNull(result); - Assert.Equal("AAPL", result.Symbol); - Assert.Equal(170.5m, result.Open); - Assert.Equal(172.1m, result.Price); - Assert.Equal(5000000, result.Volume); - Assert.Equal(today, result.LatestTradingDay.Date); - } - - - [Fact] - public async Task GetStockQuoteAsync_ThrowsOnRateLimit() - { - var rateLimitResponse = JsonConvert.SerializeObject(new { Note = "Rate limit exceeded" }); - - _handlerMock.Protected() - .Setup>("SendAsync", - ItExpr.IsAny(), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(rateLimitResponse) - }); - - await Assert.ThrowsAsync(() => _service.GetStockQuoteAsync("AAPL")); - } - - [Fact] - public async Task GetStockQuoteAsync_ThrowsOnErrorMessage() - { - var errorResponse = JsonConvert.SerializeObject(new { ErrorMessage = "Invalid API call" }); - - _handlerMock.Protected() - .Setup>("SendAsync", - ItExpr.IsAny(), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(errorResponse) - }); - - await Assert.ThrowsAsync(() => _service.GetStockQuoteAsync("INVALID")); - } + StatusCode = HttpStatusCode.OK, + Content = new StringContent(rateLimitResponse) + }); + + await Assert.ThrowsAsync(() => _service.GetStockQuoteAsync("AAPL")); + } + + [Fact] + public async Task GetStockQuoteAsync_ThrowsOnErrorMessage() + { + var errorResponse = JsonConvert.SerializeObject(new { ErrorMessage = "Invalid API call" }); + + _handlerMock.Protected() + .Setup>("SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(errorResponse) + }); + + await Assert.ThrowsAsync(() => _service.GetStockQuoteAsync("INVALID")); } } diff --git a/XUnitTest/ServiceTests/DataCleanupBackgroundServiceTests.cs b/XUnitTest/ServiceTests/DataCleanupBackgroundServiceTests.cs index cc35f21..2ba333b 100644 --- a/XUnitTest/ServiceTests/DataCleanupBackgroundServiceTests.cs +++ b/XUnitTest/ServiceTests/DataCleanupBackgroundServiceTests.cs @@ -1,44 +1,38 @@ -using DatabaseProjectAPI.Actions; -using DatabaseProjectAPI.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; +namespace XUnitTests.ServiceTests; -namespace XUnitTests.ServiceTests +public class DataCleanupBackgroundServiceTests { - public class DataCleanupBackgroundServiceTests + [Fact] + public async Task ExecuteAsync_CallsAutoDeleteMethodsOnce() { - [Fact] - public async Task ExecuteAsync_CallsAutoDeleteMethodsOnce() - { - // Arrange - var autoDeleteServiceMock = new Mock(); - autoDeleteServiceMock.Setup(x => x.DeleteOldStockHistoryAsync(It.IsAny())).Returns(Task.CompletedTask); - autoDeleteServiceMock.Setup(x => x.DeleteOldApiCallLogsAsync(It.IsAny())).Returns(Task.CompletedTask); + // Arrange + var autoDeleteServiceMock = new Mock(); + autoDeleteServiceMock.Setup(x => x.DeleteOldStockHistoryAsync(It.IsAny())).Returns(Task.CompletedTask); + autoDeleteServiceMock.Setup(x => x.DeleteOldApiCallLogsAsync(It.IsAny())).Returns(Task.CompletedTask); - var serviceProviderMock = new Mock(); - var serviceScopeMock = new Mock(); - var scopeFactoryMock = new Mock(); + var scopedServiceProviderMock = new Mock(); + scopedServiceProviderMock.Setup(x => x.GetService(typeof(IAutoDeleteService))).Returns(autoDeleteServiceMock.Object); - serviceScopeMock.Setup(x => x.ServiceProvider).Returns(serviceProviderMock.Object); - scopeFactoryMock.Setup(x => x.CreateScope()).Returns(serviceScopeMock.Object); + var scopeMock = new Mock(); + scopeMock.Setup(x => x.ServiceProvider).Returns(scopedServiceProviderMock.Object); - serviceProviderMock.Setup(x => x.GetService(typeof(IServiceScopeFactory))).Returns(scopeFactoryMock.Object); - serviceProviderMock.Setup(x => x.GetRequiredService()).Returns(autoDeleteServiceMock.Object); + var scopeFactoryMock = new Mock(); + scopeFactoryMock.Setup(x => x.CreateScope()).Returns(scopeMock.Object); - var loggerMock = new Mock>(); + var rootServiceProviderMock = new Mock(); + rootServiceProviderMock.Setup(x => x.GetService(typeof(IServiceScopeFactory))).Returns(scopeFactoryMock.Object); - var service = new DataCleanupBackgroundService(serviceProviderMock.Object, loggerMock.Object); + var loggerMock = new Mock>(); - using var cts = new CancellationTokenSource(); - cts.CancelAfter(TimeSpan.FromSeconds(1)); // Short-circuit the while loop for test + var service = new DataCleanupBackgroundService(rootServiceProviderMock.Object, loggerMock.Object); - // Act - await service.StartAsync(cts.Token); + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1)); // short delay - // Assert - autoDeleteServiceMock.Verify(x => x.DeleteOldStockHistoryAsync(It.IsAny()), Times.AtLeastOnce); - autoDeleteServiceMock.Verify(x => x.DeleteOldApiCallLogsAsync(It.IsAny()), Times.AtLeastOnce); - } + // Act + await service.StartAsync(cts.Token); + + // Assert + autoDeleteServiceMock.Verify(x => x.DeleteOldStockHistoryAsync(It.IsAny()), Times.AtLeastOnce); + autoDeleteServiceMock.Verify(x => x.DeleteOldApiCallLogsAsync(It.IsAny()), Times.AtLeastOnce); } } diff --git a/XUnitTest/ServiceTests/EventsBackgroundServiceTests.cs b/XUnitTest/ServiceTests/EventsBackgroundServiceTests.cs index 0ad7159..878a314 100644 --- a/XUnitTest/ServiceTests/EventsBackgroundServiceTests.cs +++ b/XUnitTest/ServiceTests/EventsBackgroundServiceTests.cs @@ -1,71 +1,63 @@ -using DatabaseProjectAPI.DataContext; -using DatabaseProjectAPI.Entities; -using DatabaseProjectAPI.Services; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; - -namespace XUnitTests.ServiceTests; - +namespace XUnitTests.ServiceTests; public class EventsBackgroundServiceTests { - private readonly Mock _serviceProviderMock; - private readonly Mock _serviceScopeMock; - private readonly Mock _scopeFactoryMock; - private readonly Mock> _loggerMock; - private readonly Mock _alphaVantageServiceMock; private readonly DpapiDbContext _dbContext; private readonly EventsBackgroundService _service; + private readonly Mock _alphaVantageServiceMock; + private readonly Mock> _loggerMock; public EventsBackgroundServiceTests() { var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; _dbContext = new DpapiDbContext(options); - _loggerMock = new Mock>(); _alphaVantageServiceMock = new Mock(); + _loggerMock = new Mock>(); + + // ServiceProvider inside scope + var scopedProviderMock = new Mock(); + scopedProviderMock.Setup(x => x.GetService(typeof(DpapiDbContext))).Returns(_dbContext); + scopedProviderMock.Setup(x => x.GetService(typeof(IAlphaVantageService))).Returns(_alphaVantageServiceMock.Object); - _serviceProviderMock = new Mock(); - _serviceScopeMock = new Mock(); - _scopeFactoryMock = new Mock(); + var scopeMock = new Mock(); + scopeMock.Setup(x => x.ServiceProvider).Returns(scopedProviderMock.Object); - _serviceScopeMock.Setup(s => s.ServiceProvider).Returns(_serviceProviderMock.Object); - _scopeFactoryMock.Setup(f => f.CreateScope()).Returns(_serviceScopeMock.Object); + var scopeFactoryMock = new Mock(); + scopeFactoryMock.Setup(x => x.CreateScope()).Returns(scopeMock.Object); - _serviceProviderMock.Setup(x => x.GetService(typeof(IServiceScopeFactory))) - .Returns(_scopeFactoryMock.Object); - _serviceProviderMock.Setup(x => x.GetService(typeof(DpapiDbContext))) - .Returns(_dbContext); - _serviceProviderMock.Setup(x => x.GetService(typeof(IAlphaVantageService))) - .Returns(_alphaVantageServiceMock.Object); + var rootProviderMock = new Mock(); + rootProviderMock.Setup(x => x.GetService(typeof(IServiceScopeFactory))).Returns(scopeFactoryMock.Object); - _service = new EventsBackgroundService(_serviceProviderMock.Object, _loggerMock.Object); + _service = new EventsBackgroundService(rootProviderMock.Object, _loggerMock.Object); } [Fact] public async Task ExecuteAsync_CallsAllFetchMethods() { - // Arrange mock data - var now = DateTime.UtcNow; + // Arrange + var today = DateTime.UtcNow.ToString("yyyy-MM-dd"); _alphaVantageServiceMock.Setup(a => a.GetInflationAsync()).ReturnsAsync( - new Inflation { Data = new List { new InflationDataPoint { Date = now.ToString("yyyy-MM-dd"), Value = "3.5" } } }); + new Inflation { Data = new List { new() { Date = today, Value = "3.5" } } }); _alphaVantageServiceMock.Setup(a => a.GetFederalInterestRateAsync()).ReturnsAsync( - new FederalInterestRate { Data = new List { new FederalInterestRateDataPoint { Date = now.ToString("yyyy-MM-dd"), Value = "4.5" } } }); + new FederalInterestRate { Data = new List { new() { Date = today, Value = "4.5" } } }); _alphaVantageServiceMock.Setup(a => a.GetUnemploymentRateAsync()).ReturnsAsync( - new UnemploymentRate { Data = new List { new UnemploymentRateDataPoint { Date = now.ToString("yyyy-MM-dd"), Value = "5.1" } } }); + new UnemploymentRate { Data = new List { new() { Date = today, Value = "5.1" } } }); _alphaVantageServiceMock.Setup(a => a.GetCPIdataAsync()).ReturnsAsync( - new CPIdata { Data = new List { new CpiDataPoint { Date = now.ToString("yyyy-MM-dd"), Value = "260.7" } } }); + new CPIdata { Data = new List { new() { Date = today, Value = "260.7" } } }); - using var cancellationTokenSource = new CancellationTokenSource(); - cancellationTokenSource.CancelAfter(200); // run briefly + var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(300)); // Act - await _service.StartAsync(cancellationTokenSource.Token); + await _service.StartAsync(cts.Token); // Assert - Assert.True(await _dbContext.Events.AnyAsync()); + var @event = await _dbContext.Events.FirstOrDefaultAsync(); + Assert.NotNull(@event); + Assert.Equal(3.5m, @event.Inflation); + Assert.Equal(4.5m, @event.FederalInterestRate); + Assert.Equal(5.1m, @event.UnemploymentRate); + Assert.Equal(260.7m, @event.CPI); } -} +} \ No newline at end of file diff --git a/XUnitTest/ServiceTests/FinnhubServiceTests.cs b/XUnitTest/ServiceTests/FinnhubServiceTests.cs index d23f408..0d66690 100644 --- a/XUnitTest/ServiceTests/FinnhubServiceTests.cs +++ b/XUnitTest/ServiceTests/FinnhubServiceTests.cs @@ -1,88 +1,77 @@ -using DatabaseProjectAPI.Entities; -using DatabaseProjectAPI.Services; -using KubsConnect.Settings; -using Moq; -using Moq.Protected; -using Newtonsoft.Json; -using System.Net; -using System.Text; - -namespace XUnitTests.ServiceTests +namespace XUnitTests.ServiceTests; +public class FinnhubServiceTests { - public class FinnhubServiceTests + private readonly Mock _handlerMock; + private readonly HttpClient _httpClient; + private readonly FinnhubService _service; + + public FinnhubServiceTests() { - private readonly Mock _handlerMock; - private readonly HttpClient _httpClient; - private readonly FinnhubService _service; + _handlerMock = new Mock(); + _httpClient = new HttpClient(_handlerMock.Object); - public FinnhubServiceTests() + var settings = new FinnhubSettings { - _handlerMock = new Mock(); - _httpClient = new HttpClient(_handlerMock.Object); - - var settings = new FinnhubSettings - { - ApiKey = "test-api-key" - }; + ApiKey = "test-api-key" + }; - _service = new FinnhubService(_httpClient, settings); - } + _service = new FinnhubService(_httpClient, settings); + } - [Fact] - public async Task GetStockDataAsync_ReturnsParsedJObject() + [Fact] + public async Task GetStockDataAsync_ReturnsParsedJObject() + { + var responseContent = new { - var responseContent = new - { - c = new[] { 150.1, 151.2 }, - t = new[] { 1617753600, 1617840000 } - }; + c = new[] { 150.1, 151.2 }, + t = new[] { 1617753600, 1617840000 } + }; - var json = JsonConvert.SerializeObject(responseContent); + var json = JsonConvert.SerializeObject(responseContent); - _handlerMock.Protected() - .Setup>( - "SendAsync", - ItExpr.IsAny(), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(json, Encoding.UTF8, "application/json") - }); + _handlerMock.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(json, Encoding.UTF8, "application/json") + }); - var result = await _service.GetStockDataAsync("AAPL", DateTime.UtcNow.AddDays(-2), DateTime.UtcNow); + var result = await _service.GetStockDataAsync("AAPL", DateTime.UtcNow.AddDays(-2), DateTime.UtcNow); - Assert.NotNull(result); - Assert.True(result.ContainsKey("c")); - } + Assert.NotNull(result); + Assert.True(result.ContainsKey("c")); + } - [Fact] - public async Task MarkStatusAsync_ReturnsDeserializedStatus() + [Fact] + public async Task MarkStatusAsync_ReturnsDeserializedStatus() + { + var marketStatus = new FinnhubMarketStatus { - var marketStatus = new FinnhubMarketStatus - { - isOpen = false, - exchange = "US" - }; + isOpen = false, + exchange = "US" + }; - var json = JsonConvert.SerializeObject(marketStatus); + var json = JsonConvert.SerializeObject(marketStatus); - _handlerMock.Protected() - .Setup>( - "SendAsync", - ItExpr.IsAny(), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(json, Encoding.UTF8, "application/json") - }); + _handlerMock.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(json, Encoding.UTF8, "application/json") + }); - var result = await _service.MarkStatusAsync(); + var result = await _service.MarkStatusAsync(); - Assert.NotNull(result); - Assert.False(result.isOpen); - Assert.Equal("US", result.exchange); - } + Assert.NotNull(result); + Assert.False(result.isOpen); + Assert.Equal("US", result.exchange); } } \ No newline at end of file diff --git a/XUnitTest/ServiceTests/NewsAPIServiceTests.cs b/XUnitTest/ServiceTests/NewsAPIServiceTests.cs index 4a96f31..b2bb894 100644 --- a/XUnitTest/ServiceTests/NewsAPIServiceTests.cs +++ b/XUnitTest/ServiceTests/NewsAPIServiceTests.cs @@ -1,73 +1,63 @@ -using DatabaseProjectAPI.Entities; -using DatabaseProjectAPI.Services; -using KubsConnect.Settings; -using Moq; -using Moq.Protected; -using Newtonsoft.Json; -using System.Net; -using System.Text; +namespace XUnitTests.ServiceTests; -namespace XUnitTests.ServiceTests +public class NewsAPIServiceTests { - public class NewsAPIServiceTests - { - private readonly Mock _handlerMock; - private readonly HttpClient _httpClient; - private readonly NewsAPIService _service; + private readonly Mock _handlerMock; + private readonly HttpClient _httpClient; + private readonly NewsAPIService _service; - public NewsAPIServiceTests() - { - _handlerMock = new Mock(MockBehavior.Strict); - _httpClient = new HttpClient(_handlerMock.Object); - var settings = new NewsSettings { ApiKey = "demo" }; + public NewsAPIServiceTests() + { + _handlerMock = new Mock(MockBehavior.Strict); + _httpClient = new HttpClient(_handlerMock.Object); + var settings = new NewsSettings { ApiKey = "demo" }; - _service = new NewsAPIService(_httpClient, settings); - } + _service = new NewsAPIService(_httpClient, settings); + } - [Fact] - public async Task GetNewsDataAsync_ReturnsArticles() + [Fact] + public async Task GetNewsDataAsync_ReturnsArticles() + { + var articles = new List
{ - var articles = new List
- { - new Article { Title = "Test Article", Url = "http://example.com", PublishedAt = DateTime.UtcNow } - }; + new Article { Title = "Test Article", Url = "http://example.com", PublishedAt = DateTime.UtcNow } + }; - var response = new NewsApiResponse { Articles = articles }; - var json = JsonConvert.SerializeObject(response); + var response = new NewsApiResponse { Articles = articles }; + var json = JsonConvert.SerializeObject(response); - _handlerMock.Protected() - .Setup>("SendAsync", - ItExpr.IsAny(), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(json, Encoding.UTF8, "application/json") - }); + _handlerMock.Protected() + .Setup>("SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(json, Encoding.UTF8, "application/json") + }); - var result = await _service.GetNewsDataAsync("apple", DateTime.UtcNow.AddDays(-1), DateTime.UtcNow); + var result = await _service.GetNewsDataAsync("apple", DateTime.UtcNow.AddDays(-1), DateTime.UtcNow); - Assert.Single(result); - Assert.Equal("Test Article", result[0].Title); - } + Assert.Single(result); + Assert.Equal("Test Article", result[0].Title); + } - [Fact] - public async Task GetNewsDataAsync_ThrowsOnError() - { - _handlerMock.Protected() - .Setup>("SendAsync", - ItExpr.IsAny(), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.BadRequest, - Content = new StringContent("Invalid request") - }); + [Fact] + public async Task GetNewsDataAsync_ThrowsOnError() + { + _handlerMock.Protected() + .Setup>("SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.BadRequest, + Content = new StringContent("Invalid request") + }); - var ex = await Assert.ThrowsAsync(() => - _service.GetNewsDataAsync("apple", DateTime.UtcNow.AddDays(-1), DateTime.UtcNow)); + var ex = await Assert.ThrowsAsync(() => + _service.GetNewsDataAsync("apple", DateTime.UtcNow.AddDays(-1), DateTime.UtcNow)); - Assert.Contains("status code", ex.Message); - } + Assert.Contains("status code", ex.Message); } -} +} \ No newline at end of file diff --git a/XUnitTest/ServiceTests/NewsBackgroundServiceTests.cs b/XUnitTest/ServiceTests/NewsBackgroundServiceTests.cs index 2ca7441..ffbc744 100644 --- a/XUnitTest/ServiceTests/NewsBackgroundServiceTests.cs +++ b/XUnitTest/ServiceTests/NewsBackgroundServiceTests.cs @@ -1,13 +1,4 @@ -using DatabaseProjectAPI.DataContext; -using DatabaseProjectAPI.Entities; -using DatabaseProjectAPI.Services; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; - -namespace XUnitTests.ServiceTests; - +namespace XUnitTests.ServiceTests; public class NewsBackgroundServiceTests { [Fact] diff --git a/XUnitTest/ServiceTests/StockQuoteBackgroundServiceTests.cs b/XUnitTest/ServiceTests/StockQuoteBackgroundServiceTests.cs index 25a92e7..6beb31d 100644 --- a/XUnitTest/ServiceTests/StockQuoteBackgroundServiceTests.cs +++ b/XUnitTest/ServiceTests/StockQuoteBackgroundServiceTests.cs @@ -1,76 +1,63 @@ -using DatabaseProjectAPI.Actions; -using DatabaseProjectAPI.DataContext; -using DatabaseProjectAPI.Entities; -using DatabaseProjectAPI.Helpers; -using DatabaseProjectAPI.Services; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; - -namespace XUnitTests.ServiceTests +namespace XUnitTests.ServiceTests; +public class StockQuoteBackgroundServiceTests { - public class StockQuoteBackgroundServiceTests + [Fact] + public async Task FetchAndSaveStockDataAsync_SavesStockAndHistory_WhenCalled() { - private readonly Mock> _loggerMock = new(); - private readonly Mock _finnhubServiceMock = new(); - private readonly Mock _alphaVantageServiceMock = new(); - private readonly Mock _apiRequestLoggerMock = new(); - private readonly Mock _autoDeleteServiceMock = new(); - private readonly DbContextOptions _dbContextOptions; + // Arrange + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; - public StockQuoteBackgroundServiceTests() - { - _dbContextOptions = new DbContextOptionsBuilder() - .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) - .Options; - } + var dbContext = new DpapiDbContext(options); + var trackedStock = new TrackedStock { Id = 1, Symbol = "AAPL", StockName = "Apple Inc." }; + dbContext.TrackedStocks.Add(trackedStock); + await dbContext.SaveChangesAsync(); - [Fact] - public async Task ExecuteAsync_FetchesAndSavesStockData_WhenMarketIsClosed() + var fakeQuote = new StockQuote { - // Arrange - var dbContext = new DpapiDbContext(_dbContextOptions); - var trackedStock = new TrackedStock { Id = 1, Symbol = "AAPL", StockName = "Apple Inc." }; - dbContext.TrackedStocks.Add(trackedStock); - await dbContext.SaveChangesAsync(); + Symbol = "AAPL", + Open = 180.00m, + Price = 185.50m, + Volume = 2000000, + LatestTradingDay = DateTime.UtcNow.Date + }; + + var mockAlphaVantage = new Mock(); + mockAlphaVantage.Setup(x => x.GetStockQuoteAsync("AAPL")).ReturnsAsync(fakeQuote); + + var mockApiLogger = new Mock(); + + var mockLogger = new Mock>(); + var mockFinnhubService = new Mock(); + + var mockServiceProvider = new Mock(); + mockServiceProvider.Setup(sp => sp.GetService(typeof(IAlphaVantageService))) + .Returns(mockAlphaVantage.Object); - _finnhubServiceMock.Setup(s => s.MarkStatusAsync()).ReturnsAsync(new FinnhubMarketStatus { isOpen = false }); - _apiRequestLoggerMock.Setup(s => s.HasMadeApiCallTodayAsync("MarketClose", trackedStock.Symbol, It.IsAny())).ReturnsAsync(false); - _alphaVantageServiceMock.Setup(s => s.GetStockQuoteAsync(trackedStock.Symbol)).ReturnsAsync(new StockQuote - { - Symbol = trackedStock.Symbol, - Open = 150, - Price = 155, - Volume = 100000, - LatestTradingDay = DateTime.UtcNow.Date - }); + var service = new StockQuoteBackgroundService( + mockServiceProvider.Object, + mockLogger.Object, + mockFinnhubService.Object + ); - var services = new ServiceCollection(); - services.AddSingleton(_ => dbContext); - services.AddSingleton(_apiRequestLoggerMock.Object); - services.AddSingleton(_autoDeleteServiceMock.Object); - services.AddSingleton(_alphaVantageServiceMock.Object); - var serviceProvider = services.BuildServiceProvider(); + var cancellationToken = new CancellationToken(); - var backgroundService = new StockQuoteBackgroundService(serviceProvider, _loggerMock.Object, _finnhubServiceMock.Object); + // Act + await service.FetchAndSaveStockDataAsync(dbContext, mockApiLogger.Object, "MarketClose", trackedStock, cancellationToken); - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + // Assert + var savedStock = await dbContext.Stocks.FirstOrDefaultAsync(); + Assert.NotNull(savedStock); + Assert.Equal(185.50m, savedStock.ClosingValue); - // Act - var runTask = backgroundService.StartAsync(cts.Token); - await Task.Delay(3000); // simulate execution time - cts.Cancel(); - await runTask; + var savedHistory = await dbContext.StockHistories.FirstOrDefaultAsync(); + Assert.NotNull(savedHistory); + Assert.Equal(180.00m, savedHistory.OpenedValue); + Assert.Equal(185.50m, savedHistory.ClosedValue); + Assert.Equal(savedStock.StockId, savedHistory.Stock.StockId); - // Assert - var savedStock = await dbContext.Stocks.FirstOrDefaultAsync(); - Assert.NotNull(savedStock); - Assert.Equal(150, savedStock.OpenValue); - Assert.Equal(155, savedStock.ClosingValue); - var history = await dbContext.StockHistories.FirstOrDefaultAsync(); - Assert.NotNull(history); - Assert.Equal(150, history.OpenedValue); - } + mockApiLogger.Verify(x => + x.LogApiCallAsync("MarketClose", "AAPL", cancellationToken), Times.Once); } } \ No newline at end of file From 28116b4bcb1cc576b1afe7275f315f87cdcecfb1 Mon Sep 17 00:00:00 2001 From: Ryan Dudley Date: Mon, 7 Apr 2025 18:18:34 -0400 Subject: [PATCH 4/6] finsihed --- .../HelpersTests/APIrequestloggerTests.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 XUnitTest/HelpersTests/APIrequestloggerTests.cs diff --git a/XUnitTest/HelpersTests/APIrequestloggerTests.cs b/XUnitTest/HelpersTests/APIrequestloggerTests.cs new file mode 100644 index 0000000..c58bdf4 --- /dev/null +++ b/XUnitTest/HelpersTests/APIrequestloggerTests.cs @@ -0,0 +1,54 @@ +namespace XUnitTests.HelperTests; +public class ApiRequestLoggerTests +{ + private readonly DpapiDbContext _dbContext; + private readonly Mock> _loggerMock; + private readonly ApiRequestLogger _logger; + + public ApiRequestLoggerTests() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + _dbContext = new DpapiDbContext(options); + _loggerMock = new Mock>(); + _logger = new ApiRequestLogger(_dbContext, _loggerMock.Object); + } + + [Fact] + public async Task HasMadeApiCallTodayAsync_ReturnsTrue_WhenLogExists() + { + var today = DateTime.UtcNow.Date; + await _dbContext.ApiCallLog.AddAsync(new ApiCallLog + { + CallDate = today, + CallType = "Quote", + Symbol = "AAPL" + }); + await _dbContext.SaveChangesAsync(); + + var result = await _logger.HasMadeApiCallTodayAsync("Quote", "AAPL", CancellationToken.None); + Assert.True(result); + } + + [Fact] + public async Task HasMadeApiCallTodayAsync_ReturnsFalse_WhenNoLogExists() + { + var result = await _logger.HasMadeApiCallTodayAsync("Quote", "MSFT", CancellationToken.None); + Assert.False(result); + } + + [Fact] + public async Task LogApiCallAsync_AddsLogSuccessfully() + { + var symbol = "GOOG"; + var callType = "News"; + + await _logger.LogApiCallAsync(callType, symbol, CancellationToken.None); + + var entry = await _dbContext.ApiCallLog.FirstOrDefaultAsync(a => a.Symbol == symbol && a.CallType == callType); + Assert.NotNull(entry); + Assert.Equal(DateTime.UtcNow.Date, entry.CallDate); + } +} From ad19e4b6c3f6920aa6fa64636342daf3823281ad Mon Sep 17 00:00:00 2001 From: Ryan Dudley Date: Tue, 8 Apr 2025 02:16:51 -0400 Subject: [PATCH 5/6] fixed files --- XUnitTest/{HelpersTests => }/APIrequestloggerTests.cs | 2 +- XUnitTest/{ServiceTests => }/AlphaVantageServiceTests.cs | 2 +- XUnitTest/{ActionTests => }/AutoDeleteActionTest.cs | 2 +- .../{ServiceTests => }/DataCleanupBackgroundServiceTests.cs | 2 +- XUnitTest/{ActionTests => }/EventActionTests.cs | 2 +- XUnitTest/{ServiceTests => }/EventsBackgroundServiceTests.cs | 2 +- XUnitTest/{ServiceTests => }/FinnhubServiceTests.cs | 2 +- XUnitTest/{ActionTests => }/MarketNewsActionTests.cs | 2 +- XUnitTest/{ServiceTests => }/NewsAPIServiceTests.cs | 2 +- XUnitTest/{ServiceTests => }/NewsBackgroundServiceTests.cs | 2 +- XUnitTest/{ActionTests => }/StockActionTests.cs | 2 +- XUnitTest/{ActionTests => }/StockHistoryActionTests.cs | 2 +- .../{ServiceTests => }/StockQuoteBackgroundServiceTests.cs | 2 +- XUnitTest/{ActionTests => }/TrackedStockActionTests.cs | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) rename XUnitTest/{HelpersTests => }/APIrequestloggerTests.cs (97%) rename XUnitTest/{ServiceTests => }/AlphaVantageServiceTests.cs (98%) rename XUnitTest/{ActionTests => }/AutoDeleteActionTest.cs (98%) rename XUnitTest/{ServiceTests => }/DataCleanupBackgroundServiceTests.cs (97%) rename XUnitTest/{ActionTests => }/EventActionTests.cs (98%) rename XUnitTest/{ServiceTests => }/EventsBackgroundServiceTests.cs (98%) rename XUnitTest/{ServiceTests => }/FinnhubServiceTests.cs (98%) rename XUnitTest/{ActionTests => }/MarketNewsActionTests.cs (98%) rename XUnitTest/{ServiceTests => }/NewsAPIServiceTests.cs (98%) rename XUnitTest/{ServiceTests => }/NewsBackgroundServiceTests.cs (98%) rename XUnitTest/{ActionTests => }/StockActionTests.cs (98%) rename XUnitTest/{ActionTests => }/StockHistoryActionTests.cs (98%) rename XUnitTest/{ServiceTests => }/StockQuoteBackgroundServiceTests.cs (98%) rename XUnitTest/{ActionTests => }/TrackedStockActionTests.cs (98%) diff --git a/XUnitTest/HelpersTests/APIrequestloggerTests.cs b/XUnitTest/APIrequestloggerTests.cs similarity index 97% rename from XUnitTest/HelpersTests/APIrequestloggerTests.cs rename to XUnitTest/APIrequestloggerTests.cs index c58bdf4..f17ac80 100644 --- a/XUnitTest/HelpersTests/APIrequestloggerTests.cs +++ b/XUnitTest/APIrequestloggerTests.cs @@ -1,4 +1,4 @@ -namespace XUnitTests.HelperTests; +namespace XUnitTests; public class ApiRequestLoggerTests { private readonly DpapiDbContext _dbContext; diff --git a/XUnitTest/ServiceTests/AlphaVantageServiceTests.cs b/XUnitTest/AlphaVantageServiceTests.cs similarity index 98% rename from XUnitTest/ServiceTests/AlphaVantageServiceTests.cs rename to XUnitTest/AlphaVantageServiceTests.cs index 0f58cf9..b8996b5 100644 --- a/XUnitTest/ServiceTests/AlphaVantageServiceTests.cs +++ b/XUnitTest/AlphaVantageServiceTests.cs @@ -1,4 +1,4 @@ -namespace XUnitTests.ServiceTests; +namespace XUnitTests; public class AlphaVantageServiceTests { private readonly Mock _handlerMock; diff --git a/XUnitTest/ActionTests/AutoDeleteActionTest.cs b/XUnitTest/AutoDeleteActionTest.cs similarity index 98% rename from XUnitTest/ActionTests/AutoDeleteActionTest.cs rename to XUnitTest/AutoDeleteActionTest.cs index 840b129..f61dc21 100644 --- a/XUnitTest/ActionTests/AutoDeleteActionTest.cs +++ b/XUnitTest/AutoDeleteActionTest.cs @@ -1,4 +1,4 @@ -namespace XUnitTests.ActionTests; +namespace XUnitTests; public class AutoDeleteActionTests { private DpapiDbContext _dbContext; diff --git a/XUnitTest/ServiceTests/DataCleanupBackgroundServiceTests.cs b/XUnitTest/DataCleanupBackgroundServiceTests.cs similarity index 97% rename from XUnitTest/ServiceTests/DataCleanupBackgroundServiceTests.cs rename to XUnitTest/DataCleanupBackgroundServiceTests.cs index 2ba333b..3ec55f4 100644 --- a/XUnitTest/ServiceTests/DataCleanupBackgroundServiceTests.cs +++ b/XUnitTest/DataCleanupBackgroundServiceTests.cs @@ -1,4 +1,4 @@ -namespace XUnitTests.ServiceTests; +namespace XUnitTests; public class DataCleanupBackgroundServiceTests { diff --git a/XUnitTest/ActionTests/EventActionTests.cs b/XUnitTest/EventActionTests.cs similarity index 98% rename from XUnitTest/ActionTests/EventActionTests.cs rename to XUnitTest/EventActionTests.cs index 10fa8ce..c0fe57c 100644 --- a/XUnitTest/ActionTests/EventActionTests.cs +++ b/XUnitTest/EventActionTests.cs @@ -1,4 +1,4 @@ -namespace XUnitTests.ActionTests; +namespace XUnitTests; public class EventsActionTests { private readonly DpapiDbContext _dbContext; diff --git a/XUnitTest/ServiceTests/EventsBackgroundServiceTests.cs b/XUnitTest/EventsBackgroundServiceTests.cs similarity index 98% rename from XUnitTest/ServiceTests/EventsBackgroundServiceTests.cs rename to XUnitTest/EventsBackgroundServiceTests.cs index 878a314..aae84cd 100644 --- a/XUnitTest/ServiceTests/EventsBackgroundServiceTests.cs +++ b/XUnitTest/EventsBackgroundServiceTests.cs @@ -1,4 +1,4 @@ -namespace XUnitTests.ServiceTests; +namespace XUnitTests; public class EventsBackgroundServiceTests { private readonly DpapiDbContext _dbContext; diff --git a/XUnitTest/ServiceTests/FinnhubServiceTests.cs b/XUnitTest/FinnhubServiceTests.cs similarity index 98% rename from XUnitTest/ServiceTests/FinnhubServiceTests.cs rename to XUnitTest/FinnhubServiceTests.cs index 0d66690..666c8f4 100644 --- a/XUnitTest/ServiceTests/FinnhubServiceTests.cs +++ b/XUnitTest/FinnhubServiceTests.cs @@ -1,4 +1,4 @@ -namespace XUnitTests.ServiceTests; +namespace XUnitTests; public class FinnhubServiceTests { private readonly Mock _handlerMock; diff --git a/XUnitTest/ActionTests/MarketNewsActionTests.cs b/XUnitTest/MarketNewsActionTests.cs similarity index 98% rename from XUnitTest/ActionTests/MarketNewsActionTests.cs rename to XUnitTest/MarketNewsActionTests.cs index acff781..653e31f 100644 --- a/XUnitTest/ActionTests/MarketNewsActionTests.cs +++ b/XUnitTest/MarketNewsActionTests.cs @@ -1,4 +1,4 @@ -namespace XUnitTests.ActionTests; +namespace XUnitTests; public class MarketNewsActionTests { private DpapiDbContext GetInMemoryDbContext() diff --git a/XUnitTest/ServiceTests/NewsAPIServiceTests.cs b/XUnitTest/NewsAPIServiceTests.cs similarity index 98% rename from XUnitTest/ServiceTests/NewsAPIServiceTests.cs rename to XUnitTest/NewsAPIServiceTests.cs index b2bb894..9c8810e 100644 --- a/XUnitTest/ServiceTests/NewsAPIServiceTests.cs +++ b/XUnitTest/NewsAPIServiceTests.cs @@ -1,4 +1,4 @@ -namespace XUnitTests.ServiceTests; +namespace XUnitTests; public class NewsAPIServiceTests { diff --git a/XUnitTest/ServiceTests/NewsBackgroundServiceTests.cs b/XUnitTest/NewsBackgroundServiceTests.cs similarity index 98% rename from XUnitTest/ServiceTests/NewsBackgroundServiceTests.cs rename to XUnitTest/NewsBackgroundServiceTests.cs index ffbc744..825236e 100644 --- a/XUnitTest/ServiceTests/NewsBackgroundServiceTests.cs +++ b/XUnitTest/NewsBackgroundServiceTests.cs @@ -1,4 +1,4 @@ -namespace XUnitTests.ServiceTests; +namespace XUnitTests; public class NewsBackgroundServiceTests { [Fact] diff --git a/XUnitTest/ActionTests/StockActionTests.cs b/XUnitTest/StockActionTests.cs similarity index 98% rename from XUnitTest/ActionTests/StockActionTests.cs rename to XUnitTest/StockActionTests.cs index 02d87ac..c0a031d 100644 --- a/XUnitTest/ActionTests/StockActionTests.cs +++ b/XUnitTest/StockActionTests.cs @@ -1,4 +1,4 @@ -namespace XUnitTests.ActionTests; +namespace XUnitTests; public class StockActionTests { private DpapiDbContext GetDbContext() diff --git a/XUnitTest/ActionTests/StockHistoryActionTests.cs b/XUnitTest/StockHistoryActionTests.cs similarity index 98% rename from XUnitTest/ActionTests/StockHistoryActionTests.cs rename to XUnitTest/StockHistoryActionTests.cs index bce5be9..5d3a1ae 100644 --- a/XUnitTest/ActionTests/StockHistoryActionTests.cs +++ b/XUnitTest/StockHistoryActionTests.cs @@ -1,4 +1,4 @@ -namespace XUnitTests.ActionTests; +namespace XUnitTests; public class StockHistoryActionTests { private DpapiDbContext CreateDbContext() diff --git a/XUnitTest/ServiceTests/StockQuoteBackgroundServiceTests.cs b/XUnitTest/StockQuoteBackgroundServiceTests.cs similarity index 98% rename from XUnitTest/ServiceTests/StockQuoteBackgroundServiceTests.cs rename to XUnitTest/StockQuoteBackgroundServiceTests.cs index 6beb31d..ef0f7e8 100644 --- a/XUnitTest/ServiceTests/StockQuoteBackgroundServiceTests.cs +++ b/XUnitTest/StockQuoteBackgroundServiceTests.cs @@ -1,4 +1,4 @@ -namespace XUnitTests.ServiceTests; +namespace XUnitTests; public class StockQuoteBackgroundServiceTests { [Fact] diff --git a/XUnitTest/ActionTests/TrackedStockActionTests.cs b/XUnitTest/TrackedStockActionTests.cs similarity index 98% rename from XUnitTest/ActionTests/TrackedStockActionTests.cs rename to XUnitTest/TrackedStockActionTests.cs index 749956e..236395b 100644 --- a/XUnitTest/ActionTests/TrackedStockActionTests.cs +++ b/XUnitTest/TrackedStockActionTests.cs @@ -1,4 +1,4 @@ -namespace XUnitTests.ActionTests; +namespace XUnitTests; public class TrackedStockActionTests { private DpapiDbContext _dbContext; From f30084a3f44267a0004ac85edf351a6d61c14b95 Mon Sep 17 00:00:00 2001 From: Ryan Dudley Date: Tue, 8 Apr 2025 02:52:45 -0400 Subject: [PATCH 6/6] testreuslts --- XUnitTest/test_times.csv | 101 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 XUnitTest/test_times.csv diff --git a/XUnitTest/test_times.csv b/XUnitTest/test_times.csv new file mode 100644 index 0000000..f79074b --- /dev/null +++ b/XUnitTest/test_times.csv @@ -0,0 +1,101 @@ +"Run","Time" +"1","1130" +"2","1128" +"3","1126" +"4","1143" +"5","1152" +"6","1154" +"7","1119" +"8","1120" +"9","1149" +"10","1146" +"11","1122" +"12","1105" +"13","1119" +"14","1109" +"15","1120" +"16","1116" +"17","1124" +"18","1119" +"19","1133" +"20","1133" +"21","1121" +"22","1128" +"23","1125" +"24","1121" +"25","1123" +"26","1112" +"27","1133" +"28","1119" +"29","1123" +"30","1107" +"31","1116" +"32","1108" +"33","1128" +"34","1119" +"35","1135" +"36","1113" +"37","1122" +"38","1118" +"39","1129" +"40","1127" +"41","1125" +"42","1111" +"43","1117" +"44","1132" +"45","1128" +"46","1128" +"47","1114" +"48","1143" +"49","1128" +"50","1126" +"51","1120" +"52","1120" +"53","1119" +"54","1123" +"55","1126" +"56","1107" +"57","1119" +"58","1121" +"59","1125" +"60","1123" +"61","1123" +"62","1124" +"63","1106" +"64","1125" +"65","1105" +"66","1128" +"67","1113" +"68","1114" +"69","1127" +"70","1129" +"71","1107" +"72","1111" +"73","1118" +"74","1121" +"75","1115" +"76","1122" +"77","1113" +"78","1111" +"79","1116" +"80","1115" +"81","1131" +"82","1117" +"83","1119" +"84","1131" +"85","1119" +"86","1114" +"87","1124" +"88","1104" +"89","1123" +"90","1119" +"91","1123" +"92","1115" +"93","1117" +"94","1113" +"95","1118" +"96","1131" +"97","1154" +"98","1162" +"99","1131" +"100","1128"