Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vs
2 changes: 2 additions & 0 deletions InvestmentPerformance.Tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/bin
/obj
201 changes: 201 additions & 0 deletions InvestmentPerformance.Tests/Controllers/InvestmentsControllerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
using AutoMapper;
using InvestmentPerformance.Controllers;
using InvestmentPerformance.DTOs;
using InvestmentPerformance.Models;
using InvestmentPerformance.Repositories;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Moq;

namespace InvestmentPerformance.Tests.Controllers
{
[TestClass]
public class InvestmentsControllerTests
{
private readonly Mock<IMapper> _mapper;

private readonly Mock<IInvestmentRepository> _investmentRepository;

private readonly InvestmentsController _controller;

private readonly HttpContext _httpContext;

public InvestmentsControllerTests()
{
_mapper = new Mock<IMapper>();
_investmentRepository = new Mock<IInvestmentRepository>();
_controller = new InvestmentsController(_mapper.Object, _investmentRepository.Object);

_httpContext = new DefaultHttpContext();
_controller.ControllerContext = new ControllerContext
{
HttpContext = _httpContext
};
_httpContext.Request.Headers["UserID"] = "1"; // Set a default UserID header for tests
}

[TestMethod]
public void GetInvestments_ReturnsInvestments()
{
// Arrange
var userId = 1;
var investments = new List<Investment>
{
new() { ID = 1, UserID = userId, StockID = 1, SharesOwned = 10, CostBasisPerShare = 100.00m, PurchaseDate = DateTime.Now.AddMonths(-5) },
new() { ID = 2, UserID = userId, StockID = 2, SharesOwned = 20, CostBasisPerShare = 200.00m, PurchaseDate = DateTime.Now.AddYears(-2) }
};
_investmentRepository.Setup(repo => repo.GetInvestmentsByUserId(userId)).ReturnsAsync(investments);
_mapper.Setup(m => m.Map<List<InvestmentDto>>(It.IsAny<IEnumerable<Investment>>()))
.Returns(new List<InvestmentDto>
{
new() { ID = 1, StockName = "Stock1" },
new() { ID = 2, StockName = "Stock2" }
});

// Act
var result = _controller.GetInvestments().Result;

// Assert
Assert.IsNotNull(result);
Assert.AreEqual(2, result?.Value?.Count());
}

[TestMethod]
public void GetInvestments_ReturnsBadRequest_WhenUserIdHeaderIsMissing()
{
// Arrange
_httpContext.Request.Headers.Clear(); // Ensure no UserID header is present

// Act
var result = _controller.GetInvestments().Result;

// Assert
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result.Result, typeof(BadRequestObjectResult));
var badRequestResult = result.Result as BadRequestObjectResult;
Assert.AreEqual("UserID header is required.", badRequestResult.Value);
}

[TestMethod]
public void GetInvestment_ReturnsInvestmentDetails()
{
// Arrange
var userId = 1;
var investmentId = 1;
var investment = new Investment
{
ID = investmentId,
UserID = userId,
StockID = 1,
SharesOwned = 10,
CostBasisPerShare = 100.00m,
PurchaseDate = DateTime.Now.AddMonths(-5)
};
_investmentRepository.Setup(repo => repo.GetInvestmentById(investmentId)).ReturnsAsync(investment);
_mapper.Setup(m => m.Map<InvestmentDetailsDto>(It.IsAny<Investment>()))
.Returns(new InvestmentDetailsDto
{
ID = investmentId,
CostBasisPerShare = 100.00m,
CurrentValue = 1500.00m,
CurrentPrice = 150.00m,
Term = "Short-Term",
TotalGainOrLoss = 500.00m,
});

// Act
var result = _controller.GetInvestment(investmentId).Result;

// Assert
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result.Value, typeof(InvestmentDetailsDto));
Assert.AreEqual(investmentId, result.Value.ID);
}

[TestMethod]
public void GetInvestment_ReturnsBadRequest_WhenHeaderIsMissing()
{
// Arrange
_httpContext.Request.Headers.Clear(); // Ensure no UserID header is present

// Act
var result = _controller.GetInvestment(5).Result;

// Assert
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result.Result, typeof(BadRequestObjectResult));
var badRequestResult = result.Result as BadRequestObjectResult;
Assert.AreEqual("UserID header is required.", badRequestResult.Value);
}

[TestMethod]
public void GetInvestment_ReturnsNotFound_WhenInvestmentDoesNotExist()
{
// Arrange
var userId = 1;
var investmentId = 1;
var investment = new Investment
{
ID = investmentId,
UserID = userId,
StockID = 1,
SharesOwned = 10,
CostBasisPerShare = 100.00m,
PurchaseDate = DateTime.Now.AddMonths(-5)
};
_investmentRepository.Setup(repo => repo.GetInvestmentById(investmentId)).ReturnsAsync(investment);
_mapper.Setup(m => m.Map<InvestmentDetailsDto>(It.IsAny<Investment>()))
.Returns(new InvestmentDetailsDto
{
ID = investmentId,
CostBasisPerShare = 100.00m,
CurrentValue = 1500.00m,
CurrentPrice = 150.00m,
Term = "Short-Term",
TotalGainOrLoss = 500.00m,
});

// Act
var result = _controller.GetInvestment(investmentId + 1).Result;

// Assert
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result.Result, typeof(NotFoundResult));
}

[TestMethod]
public void GetInvestment_ReturnsUnauthorized_WhenAccessingOthersInvestment()
{
// Arrange
var userId = 1;
var investmentId = 1;
var investment = new Investment
{
ID = investmentId,
UserID = userId + 1, // Different user
StockID = 1,
SharesOwned = 10,
CostBasisPerShare = 100.00m,
PurchaseDate = DateTime.Now.AddMonths(-5)
};
_investmentRepository.Setup(repo => repo.GetInvestmentById(investmentId)).ReturnsAsync(investment);
_mapper.Setup(m => m.Map<InvestmentDetailsDto>(It.IsAny<Investment>()))
.Returns(new InvestmentDetailsDto
{
ID = investmentId,
CostBasisPerShare = 100.00m,
CurrentValue = 1500.00m,
CurrentPrice = 150.00m,
Term = "Short-Term",
TotalGainOrLoss = 500.00m,
});

// Act
var result = _controller.GetInvestment(investmentId).Result;

// Assert
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result.Result, typeof(UnauthorizedResult));
}
}
}
35 changes: 35 additions & 0 deletions InvestmentPerformance.Tests/InvestmentPerformance.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EnableMSTestRunner>true</EnableMSTestRunner>
<OutputType>Exe</OutputType>
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
<!--
Displays error on console in addition to the log file. Note that this feature comes with a performance impact.
For more information, visit https://learn.microsoft.com/dotnet/core/testing/unit-testing-platform-integration-dotnet-test#show-failure-per-test
-->
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoMapper" Version="15.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" Version="17.12.6" />
<PackageReference Include="Microsoft.Testing.Extensions.TrxReport" Version="1.4.3" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="MSTest" Version="3.6.4" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\InvestmentPerformance\InvestmentPerformance.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions InvestmentPerformance.Tests/MSTestSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
2 changes: 2 additions & 0 deletions InvestmentPerformance/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/bin
/obj
77 changes: 77 additions & 0 deletions InvestmentPerformance/Controllers/InvestmentsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using AutoMapper;
using InvestmentPerformance.DTOs;
using InvestmentPerformance.Repositories;
using Microsoft.AspNetCore.Mvc;

namespace InvestmentPerformance.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class InvestmentsController : ControllerBase
{
private readonly IMapper _mapper;
private readonly IInvestmentRepository _investmentRepository;

public InvestmentsController(IMapper mapper, IInvestmentRepository investmentRepository)
{
_mapper = mapper;
_investmentRepository = investmentRepository;
}

// GET: api/Investments
[HttpGet]
public async Task<ActionResult<IEnumerable<InvestmentDto>>> GetInvestments()
{
var userId = GetUserId();

if (!userId.HasValue)
{
return BadRequest("UserID header is required.");
}

var investments = await _investmentRepository.GetInvestmentsByUserId(userId.Value);

return _mapper.Map<List<InvestmentDto>>(investments);
}

// GET: api/Investments/5
[HttpGet("{id}")]
public async Task<ActionResult<InvestmentDetailsDto>> GetInvestment(int id)
{
var userId = GetUserId();

if (userId == null)
{
return BadRequest("UserID header is required.");
}

var investment = await _investmentRepository.GetInvestmentById(id);

if (investment == null)
{
return NotFound();
}

if (investment.UserID != userId)
{
return Unauthorized(); // 403 would be better here
}

var investmentDto = _mapper.Map<InvestmentDetailsDto>(investment);

return investmentDto;
}

private int? GetUserId()
{
HttpContext.Request.Headers.TryGetValue("UserID", out var userIdHeader);

if (string.IsNullOrEmpty(userIdHeader) || !int.TryParse(userIdHeader, out var userId))
{
return null;
}

return userId;
}
}
}
13 changes: 13 additions & 0 deletions InvestmentPerformance/DTOs/InvestmentDetailsDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace InvestmentPerformance.DTOs
{
public class InvestmentDetailsDto
{
public int ID { get; set; }
public decimal CostBasisPerShare { get; set; }
public decimal CurrentValue { get; set; }
public decimal CurrentPrice { get; set; }
public required string Term { get; set; } // "Short-Term" or "Long-Term"
public decimal TotalGainOrLoss { get; set; }

}
}
8 changes: 8 additions & 0 deletions InvestmentPerformance/DTOs/InvestmentDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace InvestmentPerformance.DTOs
{
public class InvestmentDto
{
public int ID { get; set; }
public required string StockName { get; set; }
}
}
44 changes: 44 additions & 0 deletions InvestmentPerformance/Data/DbInitializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using InvestmentPerformance.Models;

namespace InvestmentPerformance.Data
{
public static class DbInitializer
{
public static void Initialize(InvestmentContext context)
{
context.Database.EnsureCreated();

// Look for any stocks.
if (context.Stocks.Any())
{
return; // DB has been seeded
}
var stocks = new Stock[]
{
new() { Name = "Apple", TickerSymbol = "AAPL", CurrentPricePerShare = 150.00m, TotalNumberOfShares = 1000000 },
new() { Name = "Microsoft", TickerSymbol = "MSFT", CurrentPricePerShare = 250.00m, TotalNumberOfShares = 800000 },
new() { Name = "Google", TickerSymbol = "GOOGL", CurrentPricePerShare = 2800.00m, TotalNumberOfShares = 500000 },
new() { Name = "Amazon", TickerSymbol = "AMZN", CurrentPricePerShare = 3400.00m, TotalNumberOfShares = 300000 },
new() { Name = "Tesla", TickerSymbol = "TSLA", CurrentPricePerShare = 700.00m, TotalNumberOfShares = 600000 }
};
foreach (Models.Stock s in stocks)
{
context.Stocks.Add(s);
}
context.SaveChanges();
var investments = new Investment[]
{
new() { UserID = 1, StockID = stocks[0].ID, SharesOwned = 50, CostBasisPerShare = 120.00m, PurchaseDate = DateTime.Parse("2021-01-15") },
new() { UserID = 1, StockID = stocks[1].ID, SharesOwned = 30, CostBasisPerShare = 200.00m, PurchaseDate = DateTime.Parse("2025-06-22") },
new() { UserID = 2, StockID = stocks[2].ID, SharesOwned = 10, CostBasisPerShare = 2500.00m, PurchaseDate = DateTime.Parse("2024-09-10") },
new() { UserID = 2, StockID = stocks[3].ID, SharesOwned = 5, CostBasisPerShare = 3200.00m, PurchaseDate = DateTime.Parse("2021-09-05") },
new() { UserID = 3, StockID = stocks[4].ID, SharesOwned = 20, CostBasisPerShare = 600.00m, PurchaseDate = DateTime.Parse("2021-11-11") }
};
foreach (Models.Investment i in investments)
{
context.Investments.Add(i);
}
context.SaveChanges();
}
}
}
Loading