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
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Build
bin/
obj/
TestResults/

# JetBrains Rider
.idea/
*.sln.iml
54 changes: 54 additions & 0 deletions InvestmentPerformance.Tests/InvestmentAPITests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using InvestmentPerformance.Api.Controllers;
using InvestmentPerformance.Api.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace InvestmentPerformance.Tests;

public class InvestmentApiTests
{
private const int ValidUserId = 101;
private const int ValidInvestmentId = 1;
private const int InvalidInvestmentId = -1;

[Fact]
public void GetUserInvestments_GivenValidUser_ReturnsOk()
{
var investmentService = new Mock<InvestmentService>();
var logger = new Mock<ILogger<InvestmentController>>();
var investmentController = new InvestmentController(investmentService.Object, logger.Object);

var result = investmentController.GetUserInvestments(ValidUserId.ToString());

Assert.IsType<OkObjectResult>(result.Result);

}

[Fact]
public void GetInvestment_GivenValidInvestment_ReturnsOk()
{
var investmentService = new Mock<InvestmentService>();
var logger = new Mock<ILogger<InvestmentController>>();
var investmentController = new InvestmentController(investmentService.Object, logger.Object);

var result = investmentController.GetInvestment(ValidInvestmentId.ToString());

Assert.IsType<OkObjectResult>(result);

}

[Fact]
public void GetInvestment_GivenInvalidInvestment_ReturnsNotFound()
{
var investmentService = new Mock<InvestmentService>();
var logger = new Mock<ILogger<InvestmentController>>();
var investmentController = new InvestmentController(investmentService.Object, logger.Object);

var result = investmentController.GetInvestment(InvalidInvestmentId.ToString());

Assert.IsType<NotFoundResult>(result);
}

}
26 changes: 26 additions & 0 deletions InvestmentPerformance.Tests/InvestmentPerformance.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.9.3"/>
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4"/>
</ItemGroup>

<ItemGroup>
<Using Include="Xunit"/>
</ItemGroup>

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

</Project>
30 changes: 30 additions & 0 deletions InvestmentPerformance.Tests/InvestmentServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using InvestmentPerformance.Api.Services;
using Xunit;

namespace InvestmentPerformance.Tests;

public class InvestmentServiceTests
{
[Fact]
public void GetInvestmentsByUserId_GivenInvalidUserId_ReturnsNothing()
{
const int userId = -1;
var investmentService = new InvestmentService();

var result = investmentService.GetInvestmentsByUserId(userId);

Assert.Empty(result);
}

[Fact]
public void GetInvestments_GivenInvalidInvestmentId_ReturnsNull()
{
const int investmentId = -1;
var investmentService = new InvestmentService();

var result = investmentService.GetInvestmentById(investmentId);

Assert.Null(result);
}

}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions InvestmentPerformance/InvestmentPerformance.Api.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="10.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="10.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.0.1" />
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions InvestmentPerformance/InvestmentPerformance.Api.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@InvestmentPerformance.Api_HostAddress = http://localhost:5283

GET {{InvestmentPerformance.Api_HostAddress}}/weatherforecast/
Accept: application/json

###
22 changes: 22 additions & 0 deletions InvestmentPerformance/InvestmentPerformance.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvestmentPerformance.Api", "InvestmentPerformance.Api.csproj", "{A220351A-4C15-4345-B30F-5B4FEB4CEFAD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvestmentPerformance.Tests", "..\InvestmentPerformance.Tests\InvestmentPerformance.Tests.csproj", "{080578E6-F472-49B0-B7EC-9FBB5703B49B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A220351A-4C15-4345-B30F-5B4FEB4CEFAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A220351A-4C15-4345-B30F-5B4FEB4CEFAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A220351A-4C15-4345-B30F-5B4FEB4CEFAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A220351A-4C15-4345-B30F-5B4FEB4CEFAD}.Release|Any CPU.Build.0 = Release|Any CPU
{080578E6-F472-49B0-B7EC-9FBB5703B49B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{080578E6-F472-49B0-B7EC-9FBB5703B49B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{080578E6-F472-49B0-B7EC-9FBB5703B49B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{080578E6-F472-49B0-B7EC-9FBB5703B49B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
25 changes: 25 additions & 0 deletions InvestmentPerformance/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using InvestmentPerformance.Api.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = WebApplication.CreateBuilder(args);

//Configure services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddScoped<IInvestmentService, InvestmentService>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.MapControllers();
app.Run();
25 changes: 25 additions & 0 deletions InvestmentPerformance/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5283",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7061;http://localhost:5283",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
22 changes: 22 additions & 0 deletions InvestmentPerformance/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# How to Run
### With IDE

- Open the solution file in an IDE and run the InvestmentPerformance.Api project
- You may need to run NuGet package restore
- Swagger should open and be able to test the api functionality

### Without IDE

- Open a command prompt window
- Navigate to the directory of the project and run ```dotnet build InvestmentPerformance.Api.csproj```
- Navigate into the project folder and run ```dotnet run```
- Open a web browser and navigate to [Swagger](htttp://localhost:5283/swagger)


# Assumptions

- I've assumed since the project will be part of a larger system the database object in SQLServer exists for an Investment, I have mocked the data in the service and created my model according to that.

- Assuming data coming in to be validated before it was committed to the DB.

- I opted not to create a front end interface for this feature since the "User stories" were about creating an API and I felt it would be out of scope and the front end would be a part of the larger system.
8 changes: 8 additions & 0 deletions InvestmentPerformance/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions InvestmentPerformance/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
65 changes: 65 additions & 0 deletions InvestmentPerformance/src/Controllers/InvestmentController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using InvestmentPerformance.Api.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace InvestmentPerformance.Api.Controllers;

[ApiController]
[Route("investments")]
public sealed class InvestmentController(IInvestmentService investmentService, ILogger<InvestmentController> logger)
: ControllerBase
{
/// <summary>
/// Retrieves a list of investments for a given user
/// </summary>
/// <param name="id">Id of the user you want investment data for</param>
/// <returns>List of UserInvestment record for the user</returns>
[HttpGet("users/{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<IEnumerable<UserInvestmentRecord>> GetUserInvestments(string id)
{
try
{
var result = investmentService.GetInvestmentsByUserId(int.Parse(id));

return Ok(result);
}
catch(Exception ex)
{
logger.LogError(ex, ex.Message);
return BadRequest();
}
}

/// <summary>
/// Retrieves the information of a given investment
/// </summary>
/// <param name="id">Id of the investment you want information on</param>
/// <returns>Investment record for the given investment</returns>
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetInvestment(string id)
{
try
{
var result = investmentService.GetInvestmentById(int.Parse(id));

if(result == null)
return NotFound();


return Ok(result);
}
catch (Exception ex)
{
logger.LogError(ex, ex.Message);
return BadRequest();
}

}
}
Loading