add openhack files
This commit is contained in:
61
apis/poi/README.md
Normal file
61
apis/poi/README.md
Normal file
@ -0,0 +1,61 @@
|
||||
# POI Service
|
||||
|
||||
## Overview
|
||||
|
||||
POI (Trip Points of Interest) - CRUD API written in .NET Core 3.1 for Points of Interest on trips. Hello there.
|
||||
|
||||
## Build & Test
|
||||
|
||||
### Restore dependencies
|
||||
|
||||
```shell
|
||||
dotnet restore
|
||||
```
|
||||
|
||||
> **NOTE:** Starting with .NET Core 2.0 SDK, you don't have to run [`dotnet restore`](https://docs.microsoft.com/dotnet/core/tools/dotnet-restore) because it's run implicitly by all commands that require a restore to occur, such as `dotnet new`, `dotnet build` and `dotnet run`.
|
||||
It's still a valid command in certain scenarios where doing an explicit restore makes sense, such as [continuous integration builds in Azure DevOps Services](https://docs.microsoft.com/azure/devops/build-release/apps/aspnet/build-aspnet-core) or in build systems that need to explicitly control the time at which the restore occurs.
|
||||
|
||||
### Build the Application
|
||||
|
||||
```shell
|
||||
dotnet build
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
You can run the test in Visual Studio/VSCode or with the command line.
|
||||
|
||||
To use the command line just type:
|
||||
|
||||
```shell
|
||||
dotnet test --logger "trx;LogFileName=TestResults.trx" --results-directory ./TestResults
|
||||
```
|
||||
|
||||
This will run both the **Unit Tests** and the **Integration Tests**.
|
||||
|
||||
#### Unit Tests
|
||||
|
||||
To run only the **Unit Tests** use filters:
|
||||
|
||||
```shell
|
||||
dotnet test --filter "FullyQualifiedName~UnitTest" --logger "trx;LogFileName=UnitTestResults.trx" --results-directory ./TestResults
|
||||
```
|
||||
|
||||
#### Integration Tests
|
||||
|
||||
To run only the **Integration Tests** use filters:
|
||||
|
||||
```shell
|
||||
dotnet test --filter "FullyQualifiedName~IntegrationTests" --logger "trx;LogFileName=IntegrationTestResults.trx" --results-directory ./TestResults
|
||||
```
|
||||
|
||||
> **NOTE:** **Integration Tests** require the Database to be available
|
||||
|
||||
## References
|
||||
|
||||
- [Web API](https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-3.1)
|
||||
- [Unit Testing](https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-dotnet-test)
|
||||
- [Integration Testing](https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1)
|
||||
- [Unit testing using dotnet test](https://github.com/dotnet/samples/tree/main/core/getting-started/unit-testing-using-dotnet-test)
|
||||
- [Run selective unit tests](https://docs.microsoft.com/en-us/dotnet/core/testing/selective-unit-tests?pivots=xunit)
|
||||
- [Logging in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.1)
|
19
apis/poi/buildtest.sh
Normal file
19
apis/poi/buildtest.sh
Normal file
@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
# clean the output of the previous build
|
||||
dotnet clean
|
||||
|
||||
# restore dependencies
|
||||
dotnet restore
|
||||
|
||||
# build the project
|
||||
dotnet build --no-restore
|
||||
|
||||
# run selective test - unit tests
|
||||
dotnet test --no-build --filter "FullyQualifiedName~UnitTest" --logger "trx;LogFileName=UnitTestResults.trx" --results-directory ./TestResults
|
||||
|
||||
# run selective test - integrations tests
|
||||
dotnet test --no-build --filter "FullyQualifiedName~IntegrationTests" --logger "trx;LogFileName=IntegrationTestResults.trx" --results-directory ./TestResults
|
||||
|
||||
# run all tests
|
||||
dotnet test --no-build
|
64
apis/poi/poi.sln
Normal file
64
apis/poi/poi.sln
Normal file
@ -0,0 +1,64 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.28010.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "poi", "web\poi.csproj", "{FEA59839-952C-41A1-A83A-CEB157623D18}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTests", "tests\IntegrationTests\IntegrationTests.csproj", "{4E03C130-2172-4F01-B408-5FD428E030F5}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "tests\UnitTests\UnitTests.csproj", "{A94A418E-6464-421F-8B33-B2C7F95049B3}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{FEA59839-952C-41A1-A83A-CEB157623D18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FEA59839-952C-41A1-A83A-CEB157623D18}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FEA59839-952C-41A1-A83A-CEB157623D18}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{FEA59839-952C-41A1-A83A-CEB157623D18}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{FEA59839-952C-41A1-A83A-CEB157623D18}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{FEA59839-952C-41A1-A83A-CEB157623D18}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{FEA59839-952C-41A1-A83A-CEB157623D18}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FEA59839-952C-41A1-A83A-CEB157623D18}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FEA59839-952C-41A1-A83A-CEB157623D18}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{FEA59839-952C-41A1-A83A-CEB157623D18}.Release|x64.Build.0 = Release|Any CPU
|
||||
{FEA59839-952C-41A1-A83A-CEB157623D18}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{FEA59839-952C-41A1-A83A-CEB157623D18}.Release|x86.Build.0 = Release|Any CPU
|
||||
{4E03C130-2172-4F01-B408-5FD428E030F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4E03C130-2172-4F01-B408-5FD428E030F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4E03C130-2172-4F01-B408-5FD428E030F5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{4E03C130-2172-4F01-B408-5FD428E030F5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{4E03C130-2172-4F01-B408-5FD428E030F5}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{4E03C130-2172-4F01-B408-5FD428E030F5}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{4E03C130-2172-4F01-B408-5FD428E030F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4E03C130-2172-4F01-B408-5FD428E030F5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4E03C130-2172-4F01-B408-5FD428E030F5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{4E03C130-2172-4F01-B408-5FD428E030F5}.Release|x64.Build.0 = Release|Any CPU
|
||||
{4E03C130-2172-4F01-B408-5FD428E030F5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{4E03C130-2172-4F01-B408-5FD428E030F5}.Release|x86.Build.0 = Release|Any CPU
|
||||
{A94A418E-6464-421F-8B33-B2C7F95049B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A94A418E-6464-421F-8B33-B2C7F95049B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A94A418E-6464-421F-8B33-B2C7F95049B3}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A94A418E-6464-421F-8B33-B2C7F95049B3}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A94A418E-6464-421F-8B33-B2C7F95049B3}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A94A418E-6464-421F-8B33-B2C7F95049B3}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A94A418E-6464-421F-8B33-B2C7F95049B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A94A418E-6464-421F-8B33-B2C7F95049B3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A94A418E-6464-421F-8B33-B2C7F95049B3}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{A94A418E-6464-421F-8B33-B2C7F95049B3}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A94A418E-6464-421F-8B33-B2C7F95049B3}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A94A418E-6464-421F-8B33-B2C7F95049B3}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {A7080A3F-644D-4630-B36A-6D9F94F341D0}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using poi.Data;
|
||||
using IntegrationTests.Utilities;
|
||||
using System.IO;
|
||||
|
||||
namespace IntegrationTests
|
||||
{
|
||||
public class CustomWebApplicationFactory<TStartup>
|
||||
: WebApplicationFactory<poi.Startup>
|
||||
{
|
||||
protected override IWebHostBuilder CreateWebHostBuilder(){
|
||||
//used to read env variables for host/port
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
|
||||
var host = new WebHostBuilder()
|
||||
.UseKestrel()
|
||||
.UseConfiguration(configuration)
|
||||
.UseIISIntegration()
|
||||
.ConfigureLogging((hostingContext, logging) =>
|
||||
{
|
||||
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
|
||||
logging.AddConsole();
|
||||
logging.AddDebug();
|
||||
})
|
||||
.UseStartup<IntegrationTests.Startup>()
|
||||
.UseUrls("http://localhost:8080");
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
builder.ConfigureServices(services =>
|
||||
{
|
||||
// Create a new service provider.
|
||||
var serviceProvider = new ServiceCollection()
|
||||
.AddEntityFrameworkInMemoryDatabase()
|
||||
.BuildServiceProvider();
|
||||
|
||||
// Add a database context (ApplicationDbContext) using an in-memory
|
||||
// database for testing.
|
||||
services.AddDbContext<POIContext>(options =>
|
||||
{
|
||||
options.UseInMemoryDatabase("InMemoryDbForTesting");
|
||||
options.UseInternalServiceProvider(serviceProvider);
|
||||
});
|
||||
|
||||
// Build the service provider.
|
||||
var sp = services.BuildServiceProvider();
|
||||
|
||||
// Create a scope to obtain a reference to the database
|
||||
// context (ApplicationDbContext).
|
||||
using (var scope = sp.CreateScope())
|
||||
{
|
||||
var scopedServices = scope.ServiceProvider;
|
||||
var db = scopedServices.GetRequiredService<POIContext>();
|
||||
var logger = scopedServices
|
||||
.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
|
||||
|
||||
// Ensure the database is created.
|
||||
db.Database.EnsureCreated();
|
||||
|
||||
try
|
||||
{
|
||||
// Seed the database with test data.
|
||||
DatabaseHelpers.InitializeDbForTests(db);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, $"An error occurred seeding the " +
|
||||
"database with test POIs. Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
30
apis/poi/tests/IntegrationTests/IntegrationTests.csproj
Normal file
30
apis/poi/tests/IntegrationTests/IntegrationTests.csproj
Normal file
@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\web\poi.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Update="xunit.runner.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
54
apis/poi/tests/IntegrationTests/POITests.cs
Normal file
54
apis/poi/tests/IntegrationTests/POITests.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using poi.Controllers;
|
||||
using poi.Models;
|
||||
using poi;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace IntegrationTests
|
||||
{
|
||||
public class POIIntegrationTests: IClassFixture<CustomWebApplicationFactory<poi.Startup>>
|
||||
{
|
||||
private readonly CustomWebApplicationFactory<poi.Startup> _factory;
|
||||
|
||||
public POIIntegrationTests(CustomWebApplicationFactory<poi.Startup> factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/api/poi/")]
|
||||
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
|
||||
{
|
||||
// Arrange
|
||||
var client = _factory.CreateClient(
|
||||
new WebApplicationFactoryClientOptions{
|
||||
BaseAddress = new Uri("http://localhost:8080")
|
||||
}
|
||||
);
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync(url);
|
||||
|
||||
|
||||
// Asserts (Check status code, content type and actual response)
|
||||
response.EnsureSuccessStatusCode(); // Status Code 200-299
|
||||
Assert.Equal("application/json; charset=utf-8",
|
||||
response.Content.Headers.ContentType.ToString());
|
||||
|
||||
//deserialize response to poi list
|
||||
List<POI> pois = JsonConvert.DeserializeObject<List<POI>>(
|
||||
await response.Content.ReadAsStringAsync());
|
||||
|
||||
//Check that 3 pois are returned
|
||||
Assert.Equal(3,
|
||||
pois.Count);
|
||||
}
|
||||
}
|
||||
}
|
46
apis/poi/tests/IntegrationTests/Startup.cs
Normal file
46
apis/poi/tests/IntegrationTests/Startup.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Newtonsoft.Json;
|
||||
using poi.Data;
|
||||
|
||||
namespace IntegrationTests
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
=> Configuration = configuration;
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddControllers()
|
||||
.AddNewtonsoftJson((options =>
|
||||
{
|
||||
options.SerializerSettings.Formatting = Formatting.Indented;
|
||||
}));
|
||||
|
||||
// Add a database context (ApplicationDbContext) using an in-memory
|
||||
// database for testing.
|
||||
services
|
||||
.AddEntityFrameworkInMemoryDatabase()
|
||||
.AddDbContext<POIContext>((serviceProvider, options) =>
|
||||
{
|
||||
options.UseInMemoryDatabase("InMemoryDbForTesting");
|
||||
options.UseInternalServiceProvider(serviceProvider);
|
||||
});
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
}
|
||||
}
|
42
apis/poi/tests/IntegrationTests/Utilities/DatabaseHelpers.cs
Normal file
42
apis/poi/tests/IntegrationTests/Utilities/DatabaseHelpers.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using poi.Data;
|
||||
using poi.Models;
|
||||
|
||||
namespace IntegrationTests.Utilities {
|
||||
public static class DatabaseHelpers {
|
||||
public static void InitializeDbForTests (POIContext db) {
|
||||
db.POIs.AddRange (GetSeedingPois ());
|
||||
db.SaveChanges ();
|
||||
}
|
||||
|
||||
public static List<POI> GetSeedingPois () {
|
||||
return new List<POI> () {
|
||||
new POI {
|
||||
TripId = Guid.NewGuid ().ToString (),
|
||||
Latitude = 0,
|
||||
Longitude = 0,
|
||||
PoiType = POIType.HardAcceleration,
|
||||
Timestamp = DateTime.Now,
|
||||
Deleted = false
|
||||
},
|
||||
new POI {
|
||||
TripId = Guid.NewGuid ().ToString (),
|
||||
Latitude = 0,
|
||||
Longitude = 0,
|
||||
PoiType = POIType.HardBrake,
|
||||
Timestamp = DateTime.Now,
|
||||
Deleted = false
|
||||
},
|
||||
new POI {
|
||||
TripId = Guid.NewGuid ().ToString (),
|
||||
Latitude = 0,
|
||||
Longitude = 0,
|
||||
PoiType = POIType.HardAcceleration,
|
||||
Timestamp = DateTime.Now,
|
||||
Deleted = false
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
5
apis/poi/tests/IntegrationTests/appsettings.json
Normal file
5
apis/poi/tests/IntegrationTests/appsettings.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"myDrivingDB": "Server=tcp:[SQL_SERVER],1433;Initial Catalog=[SQL_DBNAME];Persist Security Info=False;User ID=[SQL_USER];Password=[SQL_PASSWORD];MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
|
||||
}
|
||||
}
|
3
apis/poi/tests/IntegrationTests/xunit.runner.json
Normal file
3
apis/poi/tests/IntegrationTests/xunit.runner.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"shadowCopy": false
|
||||
}
|
274
apis/poi/tests/UnitTests/ControllerTests/POIControllerTests.cs
Normal file
274
apis/poi/tests/UnitTests/ControllerTests/POIControllerTests.cs
Normal file
@ -0,0 +1,274 @@
|
||||
using Xunit;
|
||||
using poi.Controllers;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using poi.Data;
|
||||
using poi.Models;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnitTests.ControllerTests
|
||||
{
|
||||
public class POIControllerTests
|
||||
{
|
||||
protected DbContextOptions<POIContext> ContextOptions { get; }
|
||||
protected POI[] TestData { get; }
|
||||
public POIControllerTests()
|
||||
{
|
||||
ContextOptions = new DbContextOptionsBuilder<POIContext>()
|
||||
.UseInMemoryDatabase("POIDatabase")
|
||||
.Options;
|
||||
TestData = POIFixture.GetData();
|
||||
Seed();
|
||||
}
|
||||
|
||||
private void Seed()
|
||||
{
|
||||
using (var context = new POIContext(ContextOptions))
|
||||
{
|
||||
context.Database.EnsureDeleted();
|
||||
context.Database.EnsureCreated();
|
||||
|
||||
context.AddRange(TestData);
|
||||
context.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAll_Returns_AllItems()
|
||||
{
|
||||
using (var context = new POIContext(ContextOptions))
|
||||
{
|
||||
//arrange
|
||||
var controller = new POIController(context);
|
||||
|
||||
//act
|
||||
var points = controller.GetAll().ToList();
|
||||
|
||||
//assert
|
||||
Assert.Equal(TestData.Length, points.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetById_WithValidId_Returns_SuccessStatus()
|
||||
{
|
||||
using (var context = new POIContext(ContextOptions))
|
||||
{
|
||||
//arrange
|
||||
var controller = new POIController(context);
|
||||
|
||||
//act
|
||||
var point = TestData.FirstOrDefault();
|
||||
|
||||
var result = controller.GetById(point.Id);
|
||||
var okResult = result as OkObjectResult;
|
||||
|
||||
//assert
|
||||
Assert.IsType<OkObjectResult>(result);
|
||||
Assert.NotNull(okResult);
|
||||
Assert.NotEqual(200, okResult.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetById_WithValidId_Returns_CorrectPoint()
|
||||
{
|
||||
using (var context = new POIContext(ContextOptions))
|
||||
{
|
||||
//arrange
|
||||
var controller = new POIController(context);
|
||||
|
||||
//act
|
||||
var point = TestData.FirstOrDefault();
|
||||
|
||||
var result = controller.GetById(point.Id);
|
||||
var okResult = result as OkObjectResult;
|
||||
var poiResult = okResult.Value as POI;
|
||||
|
||||
//assert
|
||||
Assert.NotNull(okResult.Value);
|
||||
Assert.Equal(point.TripId, poiResult.TripId);
|
||||
Assert.Equal(point.Latitude, poiResult.Latitude);
|
||||
Assert.Equal(point.Longitude, poiResult.Longitude);
|
||||
Assert.Equal(point.PoiType, poiResult.PoiType);
|
||||
Assert.Equal(point.Deleted, poiResult.Deleted);
|
||||
Assert.Equal(point.Timestamp, poiResult.Timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetById_WithInvalidId_Returns_NotFoundResult()
|
||||
{
|
||||
using (var context = new POIContext(ContextOptions))
|
||||
{
|
||||
//arrange
|
||||
var controller = new POIController(context);
|
||||
|
||||
//act
|
||||
var point = TestData.FirstOrDefault();
|
||||
|
||||
var result = controller.GetById("fake_id");
|
||||
|
||||
//assert
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<NotFoundResult>(result);
|
||||
|
||||
var notFoundResult = result as NotFoundResult;
|
||||
Assert.Equal(404, notFoundResult.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetByTripId_WithValidTripId_Returns_SuccessStatus()
|
||||
{
|
||||
using (var context = new POIContext(ContextOptions))
|
||||
{
|
||||
//arrange
|
||||
var controller = new POIController(context);
|
||||
|
||||
//act
|
||||
var point = TestData.FirstOrDefault();
|
||||
|
||||
var result = controller.GetByTripId(point.TripId);
|
||||
var okResult = result as OkObjectResult;
|
||||
|
||||
//assert
|
||||
Assert.IsType<OkObjectResult>(result);
|
||||
Assert.NotNull(okResult);
|
||||
Assert.Equal(200, okResult.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetByTripId_WithValidTripId_Returns_CorrectPoint()
|
||||
{
|
||||
using (var context = new POIContext(ContextOptions))
|
||||
{
|
||||
//arrange
|
||||
var controller = new POIController(context);
|
||||
|
||||
//act
|
||||
var point = TestData.FirstOrDefault();
|
||||
|
||||
var result = controller.GetByTripId(point.TripId);
|
||||
var okResult = result as OkObjectResult;
|
||||
var poiResults = okResult.Value as List<POI>;
|
||||
var poiResult = poiResults.FirstOrDefault();
|
||||
|
||||
//assert
|
||||
Assert.NotNull(okResult.Value);
|
||||
Assert.Equal(point.TripId, poiResult.TripId);
|
||||
Assert.Equal(point.Latitude, poiResult.Latitude);
|
||||
Assert.Equal(point.Longitude, poiResult.Longitude);
|
||||
Assert.Equal(point.PoiType, poiResult.PoiType);
|
||||
Assert.Equal(point.Deleted, poiResult.Deleted);
|
||||
Assert.Equal(point.Timestamp, poiResult.Timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetByTripId_WithInvalidTripId_Returns_OkObjectResult()
|
||||
{
|
||||
using (var context = new POIContext(ContextOptions))
|
||||
{
|
||||
//arrange
|
||||
var controller = new POIController(context);
|
||||
|
||||
//act
|
||||
var point = TestData.FirstOrDefault();
|
||||
|
||||
var result = controller.GetByTripId("fake_trip_id");
|
||||
|
||||
//assert
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<OkObjectResult>(result);
|
||||
|
||||
var poiResult = result as OkObjectResult;
|
||||
Assert.Equal(200, poiResult.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetByTripId_WithInvalidTripId_Returns_EmptyList()
|
||||
{
|
||||
using (var context = new POIContext(ContextOptions))
|
||||
{
|
||||
//arrange
|
||||
var controller = new POIController(context);
|
||||
|
||||
//act
|
||||
var point = TestData.FirstOrDefault();
|
||||
|
||||
var result = controller.GetByTripId("fake_trip_id");
|
||||
|
||||
//assert
|
||||
var poiResult = result as OkObjectResult;
|
||||
var poiList = poiResult.Value as List<POI>;
|
||||
Assert.Empty(poiList);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreatePoi_WithValidPoint_AddsPointToDb()
|
||||
{
|
||||
using (var context = new POIContext(ContextOptions))
|
||||
{
|
||||
//arrange
|
||||
var controller = new POIController(context);
|
||||
var point = new POI{
|
||||
TripId = "8675309",
|
||||
Latitude=35.6262904,
|
||||
Longitude=139.780985,
|
||||
PoiType = POIType.HardBrake,
|
||||
Timestamp = DateTime.Now
|
||||
};
|
||||
//act
|
||||
controller.CreatePoi(point);
|
||||
|
||||
var response = controller.GetByTripId("8675309") as OkObjectResult;
|
||||
var results = response.Value as List<POI>;
|
||||
var result = results.FirstOrDefault();
|
||||
|
||||
//assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(point.Latitude,result.Latitude);
|
||||
Assert.Equal(point.Longitude,result.Longitude);
|
||||
Assert.Equal(point.TripId,result.TripId);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreatePoi_WithValidPoint_AddGuidToPOI()
|
||||
{
|
||||
using (var context = new POIContext(ContextOptions))
|
||||
{
|
||||
//arrange
|
||||
var controller = new POIController(context);
|
||||
var point = new POI{
|
||||
TripId = "8675309",
|
||||
Latitude=35.6262904,
|
||||
Longitude=139.780985,
|
||||
PoiType = POIType.HardBrake,
|
||||
Timestamp = DateTime.Now
|
||||
};
|
||||
//act
|
||||
controller.CreatePoi(point);
|
||||
|
||||
var response = controller.GetByTripId("8675309") as OkObjectResult;
|
||||
var results = response.Value as List<POI>;
|
||||
var result = results.FirstOrDefault();
|
||||
|
||||
//assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(point.Latitude,result.Latitude);
|
||||
Assert.Equal(point.Longitude,result.Longitude);
|
||||
Assert.Equal(point.TripId,result.TripId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
using Xunit;
|
||||
using poi.Controllers;
|
||||
using System;
|
||||
|
||||
namespace UnitTests.ControllerTests
|
||||
{
|
||||
public class VersionControllerTests
|
||||
{
|
||||
|
||||
[Fact]
|
||||
public void Returns_Default_If_EnvironmentVariable_NotSet()
|
||||
{
|
||||
//arrange
|
||||
//explicitly set this to null as to clear any previous state
|
||||
Environment.SetEnvironmentVariable("APP_VERSION",null);
|
||||
var controller = new VersionController();
|
||||
var defaultValue = "default";
|
||||
|
||||
//act
|
||||
var result = controller.GetVersion();
|
||||
|
||||
//assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(defaultValue,result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Returns_AppVersion_FromEnvironmentVariable()
|
||||
{
|
||||
//arrange
|
||||
var version = "fake_test_version";
|
||||
Environment.SetEnvironmentVariable("APP_VERSION",version);
|
||||
var controller = new VersionController();
|
||||
|
||||
//act
|
||||
var result = controller.GetVersion();
|
||||
|
||||
//assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(version,result);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
28
apis/poi/tests/UnitTests/TestData/POIFixture.cs
Normal file
28
apis/poi/tests/UnitTests/TestData/POIFixture.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using poi.Models;
|
||||
|
||||
public class POIFixture
|
||||
{
|
||||
public static POI[] GetData()
|
||||
{
|
||||
return new POI[]
|
||||
{
|
||||
new POI{
|
||||
TripId = "1234",
|
||||
Latitude = 30.021530,
|
||||
Longitude = 31.071170,
|
||||
PoiType = POIType.HardAcceleration,
|
||||
Timestamp = DateTime.Now,
|
||||
Deleted = false
|
||||
},
|
||||
new POI{
|
||||
TripId = "5678",
|
||||
Latitude = 12.0075934,
|
||||
Longitude = 120.200048,
|
||||
PoiType = POIType.HardAcceleration,
|
||||
Timestamp = DateTime.Now,
|
||||
Deleted = false
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
27
apis/poi/tests/UnitTests/UnitTests.csproj
Normal file
27
apis/poi/tests/UnitTests/UnitTests.csproj
Normal file
@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.msbuild" Version="2.8.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\web\poi.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
33
apis/poi/tests/UnitTests/Utility/HealthCheckTests.cs
Normal file
33
apis/poi/tests/UnitTests/Utility/HealthCheckTests.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using Xunit;
|
||||
using poi.Controllers;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using poi.Data;
|
||||
using poi.Models;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using poi.Utility;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace UnitTests.Utility
|
||||
{
|
||||
public class HealthCheckTests
|
||||
{
|
||||
|
||||
[Fact]
|
||||
public async void CheckHealthAsync_Returns_Result()
|
||||
{
|
||||
//arrange
|
||||
CancellationToken token = new CancellationToken();
|
||||
HealthCheck healthCheck = new HealthCheck();
|
||||
//act
|
||||
HealthCheckResult result = await healthCheck.CheckHealthAsync(null,token);
|
||||
//assert
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
91
apis/poi/tests/UnitTests/Utility/POIConfigurationTests.cs
Normal file
91
apis/poi/tests/UnitTests/Utility/POIConfigurationTests.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using Xunit;
|
||||
using poi.Utility;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace UnitTests.Utility
|
||||
{
|
||||
|
||||
public class POIConfigurationTests
|
||||
{
|
||||
|
||||
private Dictionary<string, string> GetTestSettings()
|
||||
{
|
||||
string connectionStringTemplate = "Server=tcp:[SQL_SERVER],1433;Initial Catalog=[SQL_DBNAME];Persist Security Info=False;User ID=[SQL_USER];Password=[SQL_PASSWORD];MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;";
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
{"SQL_USER", "user1"},
|
||||
{"SQL_PASSWORD", "password2"},
|
||||
{"SQL_SERVER", "sqlserver3"},
|
||||
{"SQL_DBNAME", "db4"},
|
||||
{"WEB_PORT", "9090"},
|
||||
{"WEB_SERVER_BASE_URI", "https://github.com"},
|
||||
{"ConnectionStrings:myDrivingDB",connectionStringTemplate}
|
||||
};
|
||||
}
|
||||
|
||||
private IConfiguration GetTestConfiguration()
|
||||
{
|
||||
var inMemorySettings = GetTestSettings();
|
||||
IConfiguration configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(inMemorySettings)
|
||||
.Build();
|
||||
return configuration;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetConnectionString_ReturnsCS_WithCorrectValuesReplaced()
|
||||
{
|
||||
//arrange
|
||||
IConfiguration configuration = GetTestConfiguration();
|
||||
|
||||
//act
|
||||
var connectionString = POIConfiguration.GetConnectionString(configuration);
|
||||
|
||||
//assert
|
||||
var expectedConnectionString = "Server=tcp:sqlserver3,1433;Initial Catalog=db4;Persist Security Info=False;User ID=user1;Password=password2;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;";
|
||||
Assert.Equal(expectedConnectionString, connectionString);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void GetUri_Returns_DefaultUriAndPort_WhenNotInSettings()
|
||||
{
|
||||
//arrange
|
||||
IConfiguration configuration = GetTestConfiguration();
|
||||
|
||||
//act
|
||||
var uri = POIConfiguration.GetUri(configuration);
|
||||
|
||||
//assert
|
||||
var expectedUri = "https://github.com:9090";
|
||||
Assert.Equal(expectedUri, uri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUri_Returns_BaseUrlAndPortFromSettings()
|
||||
{
|
||||
//arrange
|
||||
var inMemorySettings = GetTestSettings();
|
||||
inMemorySettings.Remove("WEB_SERVER_BASE_URI");
|
||||
inMemorySettings.Remove("WEB_PORT");
|
||||
IConfiguration configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(inMemorySettings)
|
||||
.Build();
|
||||
|
||||
//act
|
||||
var uri = POIConfiguration.GetUri(configuration);
|
||||
|
||||
//assert
|
||||
var expectedUri = "http://localhost:8080";
|
||||
Assert.Equal(expectedUri, uri);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
17
apis/poi/tests/UnitTests/UtilityTests.cs
Normal file
17
apis/poi/tests/UnitTests/UtilityTests.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using Xunit;
|
||||
using poi.Utility;
|
||||
|
||||
namespace UnitTests
|
||||
{
|
||||
public class UtilityTests
|
||||
{
|
||||
[Fact]
|
||||
public void TestLoggingEvents()
|
||||
{
|
||||
Assert.Equal(1000, LoggingEvents.Healthcheck);
|
||||
Assert.Equal(2001, LoggingEvents.GetAllPOIs);
|
||||
Assert.Equal(2002, LoggingEvents.GetPOIByID);
|
||||
Assert.NotEqual(2002, LoggingEvents.GetPOIByTripID);
|
||||
}
|
||||
}
|
||||
}
|
264
apis/poi/web/.gitignore
vendored
Normal file
264
apis/poi/web/.gitignore
vendored
Normal file
@ -0,0 +1,264 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# Azure Functions localsettings file
|
||||
local.settings.json
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# DNX
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
#*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignoreable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
orleans.codegen.cs
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
64
apis/poi/web/Controllers/POIController.cs
Normal file
64
apis/poi/web/Controllers/POIController.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using poi.Models;
|
||||
using poi.Data;
|
||||
|
||||
namespace poi.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api/poi")]
|
||||
public class POIController : ControllerBase
|
||||
{
|
||||
private readonly POIContext _context;
|
||||
|
||||
public POIController(POIContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
[HttpGet(Name = "GetAllPOIs")]
|
||||
[Produces("application/json", Type = typeof(POI))]
|
||||
public List<POI> GetAll()
|
||||
{
|
||||
return _context.POIs.ToList();
|
||||
}
|
||||
|
||||
[HttpGet("{ID}", Name = "GetPOIById")]
|
||||
[Produces("application/json", Type = typeof(POI))]
|
||||
public IActionResult GetById(string ID)
|
||||
{
|
||||
var item = _context.POIs.Find(ID);
|
||||
if (item == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return Ok(item);
|
||||
}
|
||||
|
||||
[HttpGet("trip/{tripID}", Name = "GetPOIsByTripId")]
|
||||
[Produces("application/json", Type = typeof(POI))]
|
||||
public IActionResult GetByTripId(string tripID)
|
||||
{
|
||||
var items = _context.POIs.Where(poi => poi.TripId == tripID).ToList<POI>();
|
||||
|
||||
if (items == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return Ok(items);
|
||||
}
|
||||
|
||||
[HttpPost(Name = "CreatePOI")]
|
||||
public IActionResult CreatePoi([FromBody] POI poi)
|
||||
{
|
||||
poi.Id = Guid.NewGuid().ToString();
|
||||
_context.POIs.Add(poi);
|
||||
_context.SaveChanges();
|
||||
|
||||
return Ok(poi);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
20
apis/poi/web/Controllers/VersionController.cs
Normal file
20
apis/poi/web/Controllers/VersionController.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
|
||||
namespace poi.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api")]
|
||||
public class VersionController : ControllerBase
|
||||
{
|
||||
[Route("version/poi")]
|
||||
[HttpGet]
|
||||
[Produces("text/plain", Type = typeof(String))]
|
||||
public string GetVersion()
|
||||
{
|
||||
var version = Environment.GetEnvironmentVariable("APP_VERSION");
|
||||
return version ?? "default";
|
||||
}
|
||||
}
|
||||
}
|
15
apis/poi/web/Data/POIContext.cs
Normal file
15
apis/poi/web/Data/POIContext.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using poi.Models;
|
||||
|
||||
namespace poi.Data
|
||||
{
|
||||
public class POIContext : DbContext
|
||||
{
|
||||
public POIContext(DbContextOptions<POIContext> options) : base(options)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DbSet<POI> POIs { get; set; }
|
||||
}
|
||||
}
|
37
apis/poi/web/Dockerfile
Normal file
37
apis/poi/web/Dockerfile
Normal file
@ -0,0 +1,37 @@
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-env
|
||||
WORKDIR /app
|
||||
|
||||
# copy csproj and restore as distinct layers
|
||||
COPY *.csproj ./
|
||||
RUN dotnet restore
|
||||
|
||||
# copy everything else and build
|
||||
COPY . ./
|
||||
RUN dotnet publish -c Release -o out
|
||||
COPY ./appsettings.*.json /app/out/
|
||||
COPY ./appsettings.json /app/out/
|
||||
|
||||
# build runtime image
|
||||
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
|
||||
WORKDIR /app
|
||||
|
||||
# docker build argument
|
||||
# This can be specified during the docker build step by adding " --build-arg build_version=<value>"
|
||||
# App version can be accessed via the uri path /api/version/poi
|
||||
# https://vsupalov.com/docker-build-pass-environment-variables/
|
||||
ARG build_version="poi default"
|
||||
|
||||
ENV SQL_USER="YourUserName" \
|
||||
SQL_PASSWORD="changeme" \
|
||||
SQL_SERVER="changeme.database.windows.net" \
|
||||
SQL_DBNAME="mydrivingDB" \
|
||||
WEB_PORT="8080" \
|
||||
WEB_SERVER_BASE_URI="http://0.0.0.0" \
|
||||
ASPNETCORE_ENVIRONMENT="Production" \
|
||||
APP_VERSION=$build_version
|
||||
|
||||
COPY --from=build-env /app/out .
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["dotnet", "poi.dll"]
|
16
apis/poi/web/Models/BaseDataObject.cs
Normal file
16
apis/poi/web/Models/BaseDataObject.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace poi.Models
|
||||
{
|
||||
public class BaseDataObject
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public BaseDataObject()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
}
|
||||
}
|
24
apis/poi/web/Models/POI.cs
Normal file
24
apis/poi/web/Models/POI.cs
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace poi.Models
|
||||
{
|
||||
|
||||
public enum POIType
|
||||
{
|
||||
HardAcceleration = 1,
|
||||
HardBrake = 2
|
||||
}
|
||||
|
||||
public class POI : BaseDataObject
|
||||
{
|
||||
public string TripId { get; set; }
|
||||
public double Latitude { get; set; }
|
||||
public double Longitude { get; set; }
|
||||
public POIType PoiType { get; set; }
|
||||
public DateTime Timestamp { get; set; }
|
||||
public bool Deleted { get; set; }
|
||||
}
|
||||
}
|
52
apis/poi/web/Program.cs
Normal file
52
apis/poi/web/Program.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using poi.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
|
||||
namespace poi
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args)
|
||||
{
|
||||
//used to read env variables for host/port
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
|
||||
var host = Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseKestrel()
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseConfiguration(configuration)
|
||||
.UseIISIntegration()
|
||||
.ConfigureLogging((hostingContext, logging) =>
|
||||
{
|
||||
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
|
||||
logging.AddConsole();
|
||||
logging.AddDebug();
|
||||
})
|
||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||
{
|
||||
var env = hostingContext.HostingEnvironment;
|
||||
config.SetBasePath(Directory.GetCurrentDirectory());
|
||||
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
|
||||
config.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
|
||||
config.AddEnvironmentVariables();
|
||||
config.AddCommandLine(args);
|
||||
})
|
||||
.UseStartup<Startup>()
|
||||
.UseUrls(POIConfiguration.GetUri(configuration));
|
||||
});
|
||||
|
||||
return host;
|
||||
}
|
||||
}
|
||||
}
|
103
apis/poi/web/Startup.cs
Normal file
103
apis/poi/web/Startup.cs
Normal file
@ -0,0 +1,103 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Rewrite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using poi.Data;
|
||||
|
||||
namespace poi
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
=> Configuration = configuration;
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddControllers()
|
||||
.AddNewtonsoftJson((options =>
|
||||
{
|
||||
options.SerializerSettings.Formatting = Formatting.Indented;
|
||||
}));
|
||||
|
||||
services.AddHealthChecks()
|
||||
.AddDbContextCheck<POIContext>()
|
||||
.AddCheck<Utility.HealthCheck>("poi_health_check");
|
||||
|
||||
var connectionString = poi.Utility.POIConfiguration.GetConnectionString(this.Configuration);
|
||||
services.AddDbContext<POIContext>(options =>
|
||||
options.UseSqlServer(connectionString));
|
||||
|
||||
// Register the Swagger generator, defining 1 or more Swagger documents
|
||||
services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("docs", new OpenApiInfo {
|
||||
Title = "Points Of Interest(POI) API",
|
||||
Version = "v1",
|
||||
Description = "API for the POI in the My Driving example app. https://github.com/Azure-Samples/openhack-devops"
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, POIContext dbcontext)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
app.UseDeveloperExceptionPage();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseRewriter(new RewriteOptions().AddRedirect("(.*)api/docs/poi$", "$1api/docs/poi/index.html"));
|
||||
|
||||
// Enable middleware to serve generated Swagger as a JSON endpoint.
|
||||
app.UseSwagger(c =>
|
||||
c.RouteTemplate = "swagger/{documentName}/poi/swagger.json"
|
||||
);
|
||||
|
||||
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
|
||||
// specifying the Swagger JSON endpoint.
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
c.SwaggerEndpoint("/swagger/docs/poi/swagger.json", "Points Of Interest(POI) API V1");
|
||||
c.DocumentTitle = "POI Swagger UI";
|
||||
c.RoutePrefix = "api/docs/poi";
|
||||
});
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
endpoints.MapHealthChecks("api/healthcheck/poi", new HealthCheckOptions()
|
||||
{
|
||||
AllowCachingResponses = false,
|
||||
ResponseWriter = HealthCheckResponse
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static Task HealthCheckResponse(HttpContext context, HealthReport result)
|
||||
{
|
||||
context.Response.ContentType = "application/json";
|
||||
|
||||
var json = new JObject(
|
||||
new JProperty("message", "POI Service Healthcheck"),
|
||||
new JProperty("status", result.Status.ToString()));
|
||||
|
||||
return context.Response.WriteAsync(
|
||||
json.ToString(Formatting.Indented));
|
||||
}
|
||||
}
|
||||
}
|
19
apis/poi/web/Utility/HealthCheck.cs
Normal file
19
apis/poi/web/Utility/HealthCheck.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace poi.Utility
|
||||
{
|
||||
public class HealthCheck : IHealthCheck
|
||||
{
|
||||
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var healthCheckResultHealthy = true; //TODO: implement a proper health check
|
||||
|
||||
if (healthCheckResultHealthy)
|
||||
return Task.FromResult(HealthCheckResult.Healthy("POI is healthy."));
|
||||
|
||||
return Task.FromResult(HealthCheckResult.Unhealthy("POI is UNHEALTHY!!!"));
|
||||
}
|
||||
}
|
||||
}
|
21
apis/poi/web/Utility/LoggingEvents.cs
Normal file
21
apis/poi/web/Utility/LoggingEvents.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace poi.Utility
|
||||
{
|
||||
// You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
|
||||
public class LoggingEvents
|
||||
{
|
||||
public const int Healthcheck = 1000;
|
||||
|
||||
public const int GetAllPOIs = 2001;
|
||||
public const int GetPOIByID = 2002;
|
||||
public const int GetPOIByTripID = 2002;
|
||||
|
||||
}
|
||||
|
||||
}
|
37
apis/poi/web/Utility/POIConfiguration.cs
Normal file
37
apis/poi/web/Utility/POIConfiguration.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace poi.Utility
|
||||
{
|
||||
public static class POIConfiguration
|
||||
{
|
||||
public static string GetConnectionString(IConfiguration configuration)
|
||||
{
|
||||
var SQL_USER = configuration.GetSection("SQL_USER").Value;
|
||||
var SQL_PASSWORD = configuration.GetSection("SQL_PASSWORD").Value;
|
||||
var SQL_SERVER = configuration.GetSection("SQL_SERVER").Value;
|
||||
var SQL_DBNAME = configuration.GetSection("SQL_DBNAME").Value;
|
||||
|
||||
var connectionString = configuration["ConnectionStrings:myDrivingDB"];
|
||||
|
||||
connectionString = connectionString.Replace("[SQL_USER]", SQL_USER);
|
||||
connectionString = connectionString.Replace("[SQL_PASSWORD]", SQL_PASSWORD);
|
||||
connectionString = connectionString.Replace("[SQL_SERVER]", SQL_SERVER);
|
||||
connectionString = connectionString.Replace("[SQL_DBNAME]", SQL_DBNAME);
|
||||
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
public static string GetUri(IConfiguration configuration)
|
||||
{
|
||||
var WEB_PORT = configuration.GetValue(typeof(string),"WEB_PORT","8080");
|
||||
var WEB_SERVER_BASE_URI = configuration.GetValue(typeof(string), "WEB_SERVER_BASE_URI", "http://localhost");
|
||||
|
||||
return WEB_SERVER_BASE_URI + ":" + WEB_PORT;
|
||||
}
|
||||
}
|
||||
}
|
13
apis/poi/web/appsettings.Development.json
Normal file
13
apis/poi/web/appsettings.Development.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"myDrivingDB": "Server=tcp:[SQL_SERVER],1433;Initial Catalog=[SQL_DBNAME];Persist Security Info=False;User ID=[SQL_USER];Password=[SQL_PASSWORD];MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
|
||||
},
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
}
|
||||
}
|
18
apis/poi/web/appsettings.Production.json
Normal file
18
apis/poi/web/appsettings.Production.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"myDrivingDB": "Server=tcp:[SQL_SERVER],1433;Initial Catalog=[SQL_DBNAME];Persist Security Info=False;User ID=[SQL_USER];Password=[SQL_PASSWORD];MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
|
||||
},
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"Debug": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"Console": {
|
||||
"LogLevel": {
|
||||
"Default": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
apis/poi/web/appsettings.Staging.json
Normal file
18
apis/poi/web/appsettings.Staging.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"myDrivingDB": "Server=tcp:[SQL_SERVER],1433;Initial Catalog=[SQL_DBNAME];Persist Security Info=False;User ID=[SQL_USER];Password=[SQL_PASSWORD];MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
|
||||
},
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"Debug": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"Console": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
apis/poi/web/appsettings.json
Normal file
5
apis/poi/web/appsettings.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"myDrivingDB": "Server=tcp:[SQL_SERVER],1433;Initial Catalog=[SQL_DBNAME];Persist Security Info=False;User ID=[SQL_USER];Password=[SQL_PASSWORD];MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
|
||||
}
|
||||
}
|
23
apis/poi/web/poi.csproj
Normal file
23
apis/poi/web/poi.csproj
Normal file
@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="wwwroot\**" />
|
||||
<Content Remove="wwwroot\**" />
|
||||
<EmbeddedResource Remove="wwwroot\**" />
|
||||
<None Remove="wwwroot\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.4.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="5.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
1
apis/trips/.dockerignore
Normal file
1
apis/trips/.dockerignore
Normal file
@ -0,0 +1 @@
|
||||
vendor
|
22
apis/trips/.gitignore
vendored
Normal file
22
apis/trips/.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
debug
|
||||
launch.json
|
||||
.DS_Store
|
||||
.vscode/
|
||||
vendor/
|
||||
glide.lock
|
||||
go.sum
|
37
apis/trips/Dockerfile
Normal file
37
apis/trips/Dockerfile
Normal file
@ -0,0 +1,37 @@
|
||||
FROM golang:1.16.8-alpine AS gobuild
|
||||
|
||||
WORKDIR /go/src/github.com/Azure-Samples/openhack-devops-team/apis/trips
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN go get
|
||||
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -o main .
|
||||
|
||||
FROM golang:1.16.8-alpine AS gorun
|
||||
|
||||
# docker build argument
|
||||
# This can be specified during the docker build step by adding " --build-arg build_version=<value>"
|
||||
# App version can be accessed via the uri path /api/version/trips
|
||||
# https://vsupalov.com/docker-build-pass-environment-variables/
|
||||
ARG build_version="trips default"
|
||||
|
||||
ENV SQL_USER="YourUserName" \
|
||||
SQL_PASSWORD="changeme" \
|
||||
SQL_SERVER="changeme.database.windows.net" \
|
||||
SQL_DBNAME="mydrivingDB" \
|
||||
WEB_PORT="80" \
|
||||
WEB_SERVER_BASE_URI="http://0.0.0.0" \
|
||||
DOCS_URI="http://localhost" \
|
||||
DEBUG_LOGGING="false" \
|
||||
APP_VERSION=$build_version
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --update \
|
||||
ca-certificates
|
||||
|
||||
COPY --from=gobuild /go/src/github.com/Azure-Samples/openhack-devops-team/apis/trips/main .
|
||||
COPY --from=gobuild /go/src/github.com/Azure-Samples/openhack-devops-team/apis/trips/api ./api/
|
||||
|
||||
CMD ["./main"]
|
66
apis/trips/README.md
Normal file
66
apis/trips/README.md
Normal file
@ -0,0 +1,66 @@
|
||||
# Trips Service
|
||||
|
||||
## Overview
|
||||
|
||||
This is the Trips API for the MyDriving service.
|
||||
|
||||
The server was generated by the [swagger-codegen](https://github.com/swagger-api/swagger-codegen) project.
|
||||
By using the [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub.
|
||||
|
||||
## Build & Test
|
||||
|
||||
### Get dependencies
|
||||
|
||||
```shell
|
||||
go get
|
||||
```
|
||||
|
||||
### Build the Application
|
||||
|
||||
```shell
|
||||
go build
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
#### Unit Tests
|
||||
|
||||
To run unit tests and get coverage report, execute:
|
||||
|
||||
```shell
|
||||
go test -v ./tripsgo -run Unit -coverprofile=trips_coverage.out -covermode=count
|
||||
```
|
||||
|
||||
To run unit tests and get coverage report and junit report use **gotestsum** tool, execute:
|
||||
|
||||
```shell
|
||||
./gotestsum --format standard-verbose --junitfile unittest_results.xml -- ./tripsgo -run Unit -coverprofile=unittest_coverage.out -covermode=count
|
||||
```
|
||||
|
||||
#### Integration Tests
|
||||
|
||||
- Add a file called .evn to the tripsgo folder with the following structure:
|
||||
|
||||
```shell
|
||||
SQL_SERVER="<-- your database server uri -- >"
|
||||
SQL_PASSWORD="<-- your login password -- >"
|
||||
SQL_USER="<-- your login user -- >"
|
||||
SQL_DBNAME="<-- your database name -- >"
|
||||
SQL_DRIVER="mssql"
|
||||
```
|
||||
|
||||
You shouldn't need to change the SQL_DRIVER variable.
|
||||
|
||||
- To run all integration tests, execute:
|
||||
|
||||
```shell
|
||||
go test -v ./tripsgo
|
||||
```
|
||||
|
||||
- To run all integration tests and get junit report use **gotestsum** tool, execute:
|
||||
|
||||
```shell
|
||||
./gotestsum --format standard-verbose --junitfile integrationtest_results.xml -- ./tripsgo
|
||||
```
|
||||
|
||||
> **NOTE** This requires an actual database connection, so the required ENV variables need to be present.
|
640
apis/trips/api/swagger.json
Normal file
640
apis/trips/api/swagger.json
Normal file
@ -0,0 +1,640 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"version": "0.0.1",
|
||||
"title": "MyDriving Trips API",
|
||||
"description": "API for the user in the My Driving example app. https://github.com/Azure-Samples/openhack-devops-team"
|
||||
},
|
||||
"basePath": "/api",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/healthcheck/trips": {
|
||||
"x-swagger-router-controller": "healthcheck",
|
||||
"get": {
|
||||
"description": "Returns healthcheck for systems looking to ensure API is up and operational",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Service is healthy",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Healthcheck"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An error occurred",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/trips": {
|
||||
"x-swagger-router-controller": "trips",
|
||||
"get": {
|
||||
"description": "Returns all trips",
|
||||
"operationId": "getAllTrips",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Trips found",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/trip"
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Unknown Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Create a trip",
|
||||
"operationId": "createTrip",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "trip",
|
||||
"in": "body",
|
||||
"description": "Trip to add",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/trip"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Trip created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/trip"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Trip contains invalid User ID",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "Trip already exists",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Unknown Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/trips/user/{userID}": {
|
||||
"x-swagger-router-controller": "trips",
|
||||
"get": {
|
||||
"description": "Returns all trips for a given user",
|
||||
"operationId": "getAllTripsForUser",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Trips found",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/trip"
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Unknown Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "userID",
|
||||
"in": "path",
|
||||
"description": "User ID",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/trips/{tripID}": {
|
||||
"x-swagger-router-controller": "trips",
|
||||
"get": {
|
||||
"description": "Get Trip by ID",
|
||||
"operationId": "getTripByID",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Trip found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/trip"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Trip not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Unknown Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tripID",
|
||||
"in": "path",
|
||||
"description": "Trip ID",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"description": "Update Trip",
|
||||
"operationId": "updateTrip",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Trip Updated",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/trip"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Trip not found"
|
||||
},
|
||||
"default": {
|
||||
"description": "Unknown Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tripID",
|
||||
"in": "path",
|
||||
"description": "Trip ID",
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "trip",
|
||||
"in": "body",
|
||||
"description": "Trip to update",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/trip"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"description": "Delete Trip By ID",
|
||||
"operationId": "deleteTrip",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Trip Deleted"
|
||||
},
|
||||
"404": {
|
||||
"description": "Trip not found"
|
||||
},
|
||||
"default": {
|
||||
"description": "Unknown Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tripID",
|
||||
"in": "path",
|
||||
"description": "Trip ID",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/trips/{tripID}/trippoints": {
|
||||
"x-swagger-router-controller": "trippoints",
|
||||
"get": {
|
||||
"description": "Get Trip Points by Trip Id",
|
||||
"operationId": "getTripPoints",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Trip Points found",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/tripPoint"
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Unknown Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tripID",
|
||||
"in": "path",
|
||||
"description": "Trip ID",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"description": "Create Trip Point for Trip",
|
||||
"operationId": "createTripPoint",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tripID",
|
||||
"in": "path",
|
||||
"description": "Trip ID",
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "tripPoint",
|
||||
"in": "body",
|
||||
"description": "Trip Point to add",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/tripPoint"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Trip Point created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/tripPoint"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Trip Point contains invalid Trip ID",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "Trip Point already exists",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Unknown Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/trips/{tripID}/trippoints/{tripPointID}": {
|
||||
"x-swagger-router-controller": "trippoints",
|
||||
"get": {
|
||||
"description": "Get Trip Point by Trip ID and Trip Point ID",
|
||||
"operationId": "getTripPointByID",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Trip Point found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/tripPoint"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Trip Point not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Unknown Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tripID",
|
||||
"in": "path",
|
||||
"description": "Trip ID",
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "tripPointID",
|
||||
"in": "path",
|
||||
"description": "Trip Point ID",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"description": "Update Trip Point",
|
||||
"operationId": "updateTripPoint",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Trip Point Updated",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/tripPoint"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Trip Point not found"
|
||||
},
|
||||
"default": {
|
||||
"description": "Unknown Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tripID",
|
||||
"in": "path",
|
||||
"description": "Trip ID",
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "tripPointID",
|
||||
"in": "path",
|
||||
"description": "Trip Point ID",
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "tripPoint",
|
||||
"in": "body",
|
||||
"description": "Trip Point to update",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/tripPoint"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"description": "Delete Trip Point By ID",
|
||||
"operationId": "deleteTripPoint",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Trip Point Deleted"
|
||||
},
|
||||
"404": {
|
||||
"description": "Trip Point not found"
|
||||
},
|
||||
"default": {
|
||||
"description": "Unknown Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error_response_default"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tripID",
|
||||
"in": "path",
|
||||
"description": "Trip ID",
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "tripPointID",
|
||||
"in": "path",
|
||||
"description": "Trip Point ID",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/swagger": {
|
||||
"x-swagger-pipe": "swagger_raw"
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"Healthcheck": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"error_response_default": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"description": "Error code (if available)",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"description": "Error Message",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"trip": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Id": {
|
||||
"type": "string",
|
||||
"description": "Trip ID",
|
||||
"minLength": 0,
|
||||
"maxLength": 128
|
||||
},
|
||||
"Name": {
|
||||
"type": "string",
|
||||
"minLength": 0,
|
||||
"maxLength": 45,
|
||||
"pattern": "^[A-Za-z \u0000-\u007f][a-zA-Z \u0000-\u007f]*$"
|
||||
},
|
||||
"UserId": {
|
||||
"type": "string",
|
||||
"description": "User's unique identity"
|
||||
},
|
||||
"RecordedtimeStamp": {
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
"EndtimeStamp": {
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
"Rating": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"IsComplete": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"HasSimulatedOBDData": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"AverageSpeed": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
},
|
||||
"FuelUsed": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
},
|
||||
"HardStops": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"HardAccelerations": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"Distance": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
},
|
||||
"CreatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"UpdatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"Deleted": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tripPoint": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Id": {
|
||||
"type": "string",
|
||||
"description": "Trip Point ID",
|
||||
"minLength": 0,
|
||||
"maxLength": 128
|
||||
},
|
||||
"TripId": {
|
||||
"type": "string",
|
||||
"description": "Trip ID",
|
||||
"minLength": 0,
|
||||
"maxLength": 128
|
||||
},
|
||||
"Latitude": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
},
|
||||
"Longitude": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
},
|
||||
"Speed": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
},
|
||||
"RecordedTimeStamp": {
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
"Sequence": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"RPM": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
},
|
||||
"ShortTermFuelBank": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
},
|
||||
"LongTermFuelBank": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
},
|
||||
"ThrottlePosition": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
},
|
||||
"RelativeThrottlePosition": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
},
|
||||
"Runtime": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
},
|
||||
"DistanceWithMalfunctionLight": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
},
|
||||
"EngineLoad": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
},
|
||||
"MassFlowRate": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
},
|
||||
"EngineFuelRate": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
},
|
||||
"VIN": {
|
||||
"type": "string"
|
||||
},
|
||||
"HasOBDData": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"HasSimulatedOBDData": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"CreatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"UpdatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"Deleted": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
apis/trips/buildtest.sh
Normal file
26
apis/trips/buildtest.sh
Normal file
@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# clean the output of the previous build
|
||||
go clean
|
||||
|
||||
# get & install dependencies
|
||||
go get
|
||||
|
||||
# build the project
|
||||
go build
|
||||
|
||||
# run unit tests
|
||||
go test -v ./tripsgo -run Unit -coverprofile=unittest_coverage.out -covermode=count
|
||||
|
||||
# run integration tests
|
||||
go test -v ./tripsgo
|
||||
|
||||
# setup gotestsum
|
||||
chmod +x install_gotestsum.sh
|
||||
./install_gotestsum.sh
|
||||
|
||||
# run unit tests using gotestsum and generate junit report
|
||||
./gotestsum --format standard-verbose --junitfile unittest_results.xml -- ./tripsgo -run Unit -coverprofile=unittest_coverage.out -covermode=count
|
||||
|
||||
# run integration test susing gotestsum and generate junit report
|
||||
./gotestsum --format standard-verbose --junitfile integrationtest_results.xml -- ./tripsgo
|
9
apis/trips/glide.yaml
Normal file
9
apis/trips/glide.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
package: github.com/Azure-Samples/openhack-devops-team/apis/trips
|
||||
import:
|
||||
- package: github.com/codemodus/swagui
|
||||
version: ~0.4.1
|
||||
- package: github.com/denisenkom/go-mssqldb
|
||||
- package: github.com/gorilla/mux
|
||||
version: ~1.7.4
|
||||
- package: github.com/stretchr/testify
|
||||
version: ~1.6.1
|
12
apis/trips/go.mod
Normal file
12
apis/trips/go.mod
Normal file
@ -0,0 +1,12 @@
|
||||
module github.com/Azure-Samples/openhack-devops-team/apis/trips
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/codemodus/swagui v0.4.1
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/denisenkom/go-mssqldb v0.10.0
|
||||
github.com/gorilla/mux v1.7.4
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
)
|
18
apis/trips/install_gotestsum.sh
Normal file
18
apis/trips/install_gotestsum.sh
Normal file
@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
# https://github.com/gotestyourself/gotestsum
|
||||
if [[ "${OSTYPE}" == "linux-gnu"* ]]; then
|
||||
os_type="linux"
|
||||
elif [[ "${OSTYPE}" == "darwin"* ]]; then
|
||||
os_type="darwin"
|
||||
fi
|
||||
|
||||
OSARCH=$(uname -m)
|
||||
if [[ "${OSARCH}" == "x86_64"* ]]; then
|
||||
os_arch="amd64"
|
||||
elif [[ "${OSARCH}" == "arm"* ]]; then
|
||||
os_arch="arm"
|
||||
fi
|
||||
|
||||
gotestsum_url=$(curl -s https://api.github.com/repos/gotestyourself/gotestsum/releases/latest | jq -c -r '.assets[] | select(.name | contains("'${os_type}'") and contains("'${os_arch}'")) | .browser_download_url')
|
||||
curl -sSL "${gotestsum_url}" | tar -xz gotestsum
|
41
apis/trips/main.go
Normal file
41
apis/trips/main.go
Normal file
@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
sw "github.com/Azure-Samples/openhack-devops-team/apis/trips/tripsgo"
|
||||
)
|
||||
|
||||
var (
|
||||
webServerPort = flag.String("webServerPort", getEnv("WEB_PORT", "8080"), "web server port")
|
||||
webServerBaseURI = flag.String("webServerBaseURI", getEnv("WEB_SERVER_BASE_URI", "changeme"), "base portion of server uri")
|
||||
)
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
var debug, present = os.LookupEnv("DEBUG_LOGGING")
|
||||
|
||||
if present && debug == "true" {
|
||||
sw.InitLogging(os.Stdout, os.Stdout, os.Stdout)
|
||||
} else {
|
||||
// if debug env is not present or false, do not log debug output to console
|
||||
sw.InitLogging(os.Stdout, ioutil.Discard, os.Stdout)
|
||||
}
|
||||
|
||||
sw.Info.Println(fmt.Sprintf("%s%s", "Trips Service Server started on port ", *webServerPort))
|
||||
|
||||
router := sw.NewRouter()
|
||||
|
||||
sw.Fatal.Println(http.ListenAndServe(fmt.Sprintf("%s%s", ":", *webServerPort), router))
|
||||
}
|
140
apis/trips/tripsgo/dataAccess.go
Normal file
140
apis/trips/tripsgo/dataAccess.go
Normal file
@ -0,0 +1,140 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
//load .env locally for integration tests
|
||||
//System Environment variables take precedence
|
||||
var dbEnv = godotenv.Load()
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
var (
|
||||
debug = flag.Bool("debug", false, "enable debugging")
|
||||
password = flag.String("password", getEnv("SQL_PASSWORD", "changeme"), "the database password")
|
||||
port = flag.Int("port", 1433, "the database port")
|
||||
server = flag.String("server", getEnv("SQL_SERVER", "changeme.database.windows.net"), "the database server")
|
||||
user = flag.String("user", getEnv("SQL_USER", "YourUserName"), "the database user")
|
||||
database = flag.String("d", getEnv("SQL_DBNAME", "mydrivingDB"), "db_name")
|
||||
driver = flag.String("driver", getEnv("SQL_DRIVER", "mssql"), "db driver")
|
||||
)
|
||||
|
||||
func RebindDataAccessEnvironmentVariables() {
|
||||
s := getEnv("SQL_SERVER", "changeme.database.windows.net")
|
||||
server = &s
|
||||
|
||||
dr := getEnv("SQL_DRIVER", "mssql")
|
||||
driver = &dr
|
||||
}
|
||||
|
||||
// ExecuteNonQuery - Execute a SQL query that has no records returned (Ex. Delete)
|
||||
func ExecuteNonQuery(query string) (string, error) {
|
||||
connString := fmt.Sprintf("server=%s;database=%s;user id=%s;password=%s;port=%d", *server, *database, *user, *password, *port)
|
||||
|
||||
if *debug {
|
||||
message := fmt.Sprintf("connString:%s\n", connString)
|
||||
logMessage(message)
|
||||
}
|
||||
|
||||
conn, err := sql.Open(*driver, connString)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
statement, err := conn.Prepare(query)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer statement.Close()
|
||||
|
||||
result, err := statement.Exec()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
serializedResult, _ := json.Marshal(result)
|
||||
|
||||
return string(serializedResult), nil
|
||||
}
|
||||
|
||||
// ExecuteQuery - Executes a query and returns the result set
|
||||
func ExecuteQuery(query string) (*sql.Rows, error) {
|
||||
connString := fmt.Sprintf("server=%s;database=%s;user id=%s;password=%s;port=%d", *server, *database, *user, *password, *port)
|
||||
|
||||
conn, err := sql.Open(*driver, connString)
|
||||
|
||||
if err != nil {
|
||||
logError(err, "Failed to connect to database.")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
statement, err := conn.Prepare(query)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// log.Fatal("Failed to query a trip: ", err.Error())
|
||||
}
|
||||
|
||||
defer statement.Close()
|
||||
|
||||
rows, err := statement.Query()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// log.Fatal("Error while running the query: ", err.Error())
|
||||
}
|
||||
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
// FirstOrDefault - returns the first row of the result set.
|
||||
func FirstOrDefault(query string) (*sql.Row, error) {
|
||||
connString := fmt.Sprintf("server=%s;database=%s;user id=%s;password=%s;port=%d", *server, *database, *user, *password, *port)
|
||||
|
||||
if *debug {
|
||||
message := fmt.Sprintf("connString:%s\n", connString)
|
||||
logMessage(message)
|
||||
}
|
||||
|
||||
conn, err := sql.Open(*driver, connString)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// log.Fatal("Failed to connect to the database: ", err.Error())
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
statement, err := conn.Prepare(query)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// log.Fatal("Failed to query a trip: ", err.Error())
|
||||
}
|
||||
|
||||
defer statement.Close()
|
||||
|
||||
row := statement.QueryRow()
|
||||
|
||||
return row, nil
|
||||
}
|
170
apis/trips/tripsgo/dataAccess_test.go
Normal file
170
apis/trips/tripsgo/dataAccess_test.go
Normal file
@ -0,0 +1,170 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestExecuteQueryInvalidDriverReturnsErr(t *testing.T) {
|
||||
defer t.Cleanup(resetDataAccessEnvVars)
|
||||
//arrange
|
||||
InitLogging(os.Stdout, os.Stdout, os.Stdout)
|
||||
os.Setenv("SQL_DRIVER", "not_a_real_driver")
|
||||
RebindDataAccessEnvironmentVariables()
|
||||
//act
|
||||
var query = SelectAllTripsForUserQuery("someUser")
|
||||
|
||||
_, err := ExecuteQuery(query)
|
||||
|
||||
//assert
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
func TestExecuteQueryConnectionSuccess(t *testing.T) {
|
||||
//act
|
||||
var query = SelectAllTripsForUserQuery("someUser")
|
||||
trips, err := ExecuteQuery(query)
|
||||
|
||||
//assert
|
||||
assert.NotNil(t, trips)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestExecuteQueryInvalidSqlReturnsErr(t *testing.T) {
|
||||
//arrange
|
||||
InitLogging(os.Stdout, os.Stdout, os.Stdout)
|
||||
|
||||
//act
|
||||
var invalidSql = "Select Trips From *"
|
||||
_, err := ExecuteQuery(invalidSql)
|
||||
|
||||
//assert
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestExecuteQueryInvalidServerReturnsErr(t *testing.T) {
|
||||
defer t.Cleanup(resetDataAccessEnvVars)
|
||||
|
||||
//arrange
|
||||
InitLogging(os.Stdout, os.Stdout, os.Stdout)
|
||||
os.Setenv("SQL_SERVER", "not_a_real_driver")
|
||||
RebindDataAccessEnvironmentVariables()
|
||||
|
||||
//act
|
||||
_, err := ExecuteQuery("SELECT TOP 1 ID FROM Trips")
|
||||
|
||||
//assert
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestExecuteNonQueryInvalidDriverReturnsErr(t *testing.T) {
|
||||
defer t.Cleanup(resetDataAccessEnvVars)
|
||||
//arrange
|
||||
InitLogging(os.Stdout, os.Stdout, os.Stdout)
|
||||
os.Setenv("SQL_DRIVER", "not_a_real_driver")
|
||||
RebindDataAccessEnvironmentVariables()
|
||||
|
||||
//act
|
||||
_, err := ExecuteNonQuery("fake non query sql")
|
||||
|
||||
//assert
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestExecuteNonQueryConnectionSuccess(t *testing.T) {
|
||||
//act
|
||||
_, err := ExecuteNonQuery("SELECT TOP 1 ID FROM Trips")
|
||||
|
||||
//assert
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestExecuteNonQueryInvalidServerReturnsErr(t *testing.T) {
|
||||
defer t.Cleanup(resetDataAccessEnvVars)
|
||||
|
||||
//arrange
|
||||
InitLogging(os.Stdout, os.Stdout, os.Stdout)
|
||||
os.Setenv("SQL_SERVER", "not_a_real_server")
|
||||
RebindDataAccessEnvironmentVariables()
|
||||
|
||||
//act
|
||||
_, err := ExecuteNonQuery("SELECT TOP 1 ID FROM Trips")
|
||||
|
||||
//assert
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestFirstOrDefaultInvalidDriverReturnsErr(t *testing.T) {
|
||||
defer t.Cleanup(resetDataAccessEnvVars)
|
||||
//arrange
|
||||
InitLogging(os.Stdout, os.Stdout, os.Stdout)
|
||||
os.Setenv("SQL_DRIVER", "not_a_real_driver")
|
||||
RebindDataAccessEnvironmentVariables()
|
||||
|
||||
//act
|
||||
_, err := FirstOrDefault("fake non query sql")
|
||||
|
||||
//assert
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestFirstOrDefaultConnectionSuccess(t *testing.T) {
|
||||
//act
|
||||
RebindDataAccessEnvironmentVariables()
|
||||
_, err := FirstOrDefault("SELECT TOP 1 ID FROM Trips")
|
||||
|
||||
//assert
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestFirstOrDefaultInvalidServerReturnsErr(t *testing.T) {
|
||||
defer t.Cleanup(resetDataAccessEnvVars)
|
||||
|
||||
//arrange
|
||||
InitLogging(os.Stdout, os.Stdout, os.Stdout)
|
||||
os.Setenv("SQL_SERVER", "not_a_real_server")
|
||||
RebindDataAccessEnvironmentVariables()
|
||||
|
||||
//act
|
||||
_, err := FirstOrDefault("SELECT TOP 1 ID FROM Trips")
|
||||
|
||||
//assert
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestExecuteNonQueryWritesLogIfDebugTrue(t *testing.T) {
|
||||
defer t.Cleanup(resetDataAccessEnvVars)
|
||||
|
||||
//arrange
|
||||
info := new(bytes.Buffer)
|
||||
InitLogging(info, os.Stdout, os.Stdout)
|
||||
var tr bool = true
|
||||
debug = &tr
|
||||
|
||||
//act
|
||||
ExecuteNonQuery("SELECT TOP 1 ID FROM Trips")
|
||||
|
||||
//assert
|
||||
actual := fmt.Sprint(info)
|
||||
assert.True(t, actual != "")
|
||||
}
|
||||
|
||||
func TestFirstOrDefaultWritesLogIfDebugTrue(t *testing.T) {
|
||||
defer t.Cleanup(resetDataAccessEnvVars)
|
||||
|
||||
//arrange
|
||||
info := new(bytes.Buffer)
|
||||
InitLogging(info, os.Stdout, os.Stdout)
|
||||
var tr bool = true
|
||||
debug = &tr
|
||||
|
||||
//act
|
||||
FirstOrDefault("SELECT TOP 1 ID FROM Trips")
|
||||
|
||||
//assert
|
||||
actual := fmt.Sprint(info)
|
||||
assert.True(t, actual != "")
|
||||
}
|
24
apis/trips/tripsgo/errorMessage.go
Normal file
24
apis/trips/tripsgo/errorMessage.go
Normal file
@ -0,0 +1,24 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SerializeError - Serialize Error information to JSON format.
|
||||
func SerializeError(e error, customMessage string) string {
|
||||
var errorMessage struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
if customMessage != "" {
|
||||
message := []string{customMessage, e.Error()}
|
||||
errorMessage.Message = strings.Join(message, ": ")
|
||||
} else {
|
||||
errorMessage.Message = e.Error()
|
||||
}
|
||||
|
||||
serializedError, _ := json.Marshal(errorMessage)
|
||||
|
||||
return string(serializedError)
|
||||
}
|
28
apis/trips/tripsgo/errorMessage_test.go
Normal file
28
apis/trips/tripsgo/errorMessage_test.go
Normal file
@ -0,0 +1,28 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSerializeErrorReturnsJsonIncludesErrorMessageUnit(t *testing.T) {
|
||||
//arrange
|
||||
expected := "{\"Message\":\"This is a fake error\"}"
|
||||
err := errors.New("This is a fake error")
|
||||
//act
|
||||
actual := SerializeError(err, "")
|
||||
//assert
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestSerializeErrorReturnsJsonIncludesCustomMessageUnit(t *testing.T) {
|
||||
//arrange
|
||||
expected := "{\"Message\":\"more data: This is a fake error\"}"
|
||||
err := errors.New("This is a fake error")
|
||||
//act
|
||||
actual := SerializeError(err, "more data")
|
||||
//assert
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
11
apis/trips/tripsgo/error_response_default.go
Normal file
11
apis/trips/tripsgo/error_response_default.go
Normal file
@ -0,0 +1,11 @@
|
||||
package tripsgo
|
||||
|
||||
// ErrorResponseDefault - Structure to return error information to service caller.
|
||||
type ErrorResponseDefault struct {
|
||||
|
||||
// Error code (if available)
|
||||
Status int32 `json:"status,omitempty"`
|
||||
|
||||
// Error Message
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
15
apis/trips/tripsgo/healthCheckService.go
Normal file
15
apis/trips/tripsgo/healthCheckService.go
Normal file
@ -0,0 +1,15 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func healthcheckGet(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
hc := &Healthcheck{Message: "Trip Service Healthcheck", Status: "Healthy"}
|
||||
|
||||
json.NewEncoder(w).Encode(hc)
|
||||
}
|
31
apis/trips/tripsgo/healthCheckService_test.go
Normal file
31
apis/trips/tripsgo/healthCheckService_test.go
Normal file
@ -0,0 +1,31 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var healthRouteTests = []APITestCase{
|
||||
{
|
||||
Tag: "t0 - healthcheck",
|
||||
Method: "GET",
|
||||
URL: "/api/healthcheck/trips",
|
||||
Status: 200,
|
||||
ExpectedResponse: `{"message": "Trip Service Healthcheck","status": "Healthy"}`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestHealthRouteUnit(t *testing.T) {
|
||||
router := NewRouter()
|
||||
var debug, present = os.LookupEnv("DEBUG_LOGGING")
|
||||
|
||||
if present && debug == "true" {
|
||||
InitLogging(os.Stdout, os.Stdout, os.Stdout)
|
||||
} else {
|
||||
// if debug env is not present or false, do not log debug output to console
|
||||
InitLogging(os.Stdout, ioutil.Discard, os.Stdout)
|
||||
}
|
||||
RunAPITests(t, router, healthRouteTests[0:1])
|
||||
|
||||
}
|
11
apis/trips/tripsgo/healthcheck.go
Normal file
11
apis/trips/tripsgo/healthcheck.go
Normal file
@ -0,0 +1,11 @@
|
||||
package tripsgo
|
||||
|
||||
// Healthcheck - Structure for healthcheck response body
|
||||
type Healthcheck struct {
|
||||
|
||||
//
|
||||
Message string `json:"message,omitempty"`
|
||||
|
||||
//
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
61
apis/trips/tripsgo/logger.go
Normal file
61
apis/trips/tripsgo/logger.go
Normal file
@ -0,0 +1,61 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
//
|
||||
var (
|
||||
Info *log.Logger
|
||||
Debug *log.Logger
|
||||
Fatal *log.Logger
|
||||
)
|
||||
|
||||
// InitLogging - Initialize logging for trips api
|
||||
func InitLogging(
|
||||
infoHandle io.Writer,
|
||||
debugHandle io.Writer,
|
||||
fatalHandle io.Writer) {
|
||||
|
||||
Info = log.New(infoHandle,
|
||||
"INFO: ",
|
||||
log.Ldate|log.Ltime|log.Lshortfile)
|
||||
|
||||
Debug = log.New(debugHandle,
|
||||
"DEBUG: ",
|
||||
log.Ldate|log.Ltime|log.Lshortfile)
|
||||
|
||||
Fatal = log.New(fatalHandle,
|
||||
"FATAL: ",
|
||||
log.Ldate|log.Ltime|log.Lshortfile)
|
||||
}
|
||||
|
||||
// Logger - basic console logger that writes request info to stdout
|
||||
func Logger(inner http.Handler, name string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
|
||||
inner.ServeHTTP(w, r)
|
||||
|
||||
Info.Println(fmt.Sprintf(
|
||||
"%s %s %s %s",
|
||||
r.Method,
|
||||
r.RequestURI,
|
||||
name,
|
||||
time.Since(start),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
func logMessage(msg string) {
|
||||
Info.Println(msg)
|
||||
}
|
||||
|
||||
func logError(err error, msg string) {
|
||||
Info.Println(msg)
|
||||
Debug.Println(err.Error())
|
||||
}
|
58
apis/trips/tripsgo/logger_test.go
Normal file
58
apis/trips/tripsgo/logger_test.go
Normal file
@ -0,0 +1,58 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLogMessagePrintToInfoLogUnit(t *testing.T) {
|
||||
//arrange
|
||||
info := new(bytes.Buffer)
|
||||
debug := new(bytes.Buffer)
|
||||
fatal := new(bytes.Buffer)
|
||||
InitLogging(info, debug, fatal)
|
||||
errorMessage := "This is a test message"
|
||||
//act
|
||||
logMessage(errorMessage)
|
||||
|
||||
//assert
|
||||
actual := fmt.Sprint(info)
|
||||
assert.True(t, strings.Contains(actual, errorMessage))
|
||||
}
|
||||
|
||||
func TestLogErrorPrintsMsgToInfoUnit(t *testing.T) {
|
||||
//arrange
|
||||
info := new(bytes.Buffer)
|
||||
debug := new(bytes.Buffer)
|
||||
fatal := new(bytes.Buffer)
|
||||
InitLogging(info, debug, fatal)
|
||||
errorMessage := "This is a test message"
|
||||
err := errors.New("This is a fake error")
|
||||
//act
|
||||
logError(err, errorMessage)
|
||||
|
||||
//assert
|
||||
actual := fmt.Sprint(info)
|
||||
assert.True(t, strings.Contains(actual, errorMessage))
|
||||
}
|
||||
|
||||
func TestLogErrorPrintsErrMessageToDebugUnit(t *testing.T) {
|
||||
//arrange
|
||||
info := new(bytes.Buffer)
|
||||
debug := new(bytes.Buffer)
|
||||
fatal := new(bytes.Buffer)
|
||||
InitLogging(info, debug, fatal)
|
||||
errorMessage := "This is a test message"
|
||||
err := errors.New("This is a fake error")
|
||||
//act
|
||||
logError(err, errorMessage)
|
||||
|
||||
//assert
|
||||
actual := fmt.Sprint(debug)
|
||||
assert.True(t, strings.Contains(actual, "This is a fake error"))
|
||||
}
|
393
apis/trips/tripsgo/queries.go
Normal file
393
apis/trips/tripsgo/queries.go
Normal file
@ -0,0 +1,393 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// SelectTripByIDQuery - REQUIRED tripID value
|
||||
func SelectTripByIDQuery(tripID string) string {
|
||||
return `SELECT
|
||||
Id,
|
||||
Name,
|
||||
UserId,
|
||||
RecordedTimeStamp,
|
||||
EndTimeStamp,
|
||||
Rating,
|
||||
IsComplete,
|
||||
HasSimulatedOBDData,
|
||||
AverageSpeed,
|
||||
FuelUsed,
|
||||
HardStops,
|
||||
HardAccelerations,
|
||||
Distance,
|
||||
CreatedAt,
|
||||
UpdatedAt
|
||||
FROM Trips
|
||||
WHERE Id = '` + tripID + `'
|
||||
AND Deleted = 0`
|
||||
}
|
||||
|
||||
var SelectAllTripsQuery = selectAllTripsQuery
|
||||
|
||||
// SelectAllTripsQuery - select all trips
|
||||
func selectAllTripsQuery() string {
|
||||
return `SELECT
|
||||
Id,
|
||||
Name,
|
||||
UserId,
|
||||
RecordedTimeStamp,
|
||||
EndTimeStamp,
|
||||
Rating,
|
||||
IsComplete,
|
||||
HasSimulatedOBDData,
|
||||
AverageSpeed,
|
||||
FuelUsed,
|
||||
HardStops,
|
||||
HardAccelerations,
|
||||
Distance,
|
||||
CreatedAt,
|
||||
UpdatedAt
|
||||
FROM Trips
|
||||
WHERE Deleted = 0`
|
||||
}
|
||||
|
||||
// SelectAllTripsForUserQuery REQUIRED userID
|
||||
var SelectAllTripsForUserQuery = selectAllTripsForUserQuery
|
||||
|
||||
func selectAllTripsForUserQuery(userID string) string {
|
||||
return `SELECT
|
||||
Id,
|
||||
Name,
|
||||
UserId,
|
||||
RecordedTimeStamp,
|
||||
EndTimeStamp,
|
||||
Rating,
|
||||
IsComplete,
|
||||
HasSimulatedOBDData,
|
||||
AverageSpeed,
|
||||
FuelUsed,
|
||||
HardStops,
|
||||
HardAccelerations,
|
||||
Distance,
|
||||
CreatedAt,
|
||||
UpdatedAt
|
||||
FROM Trips
|
||||
WHERE UserId ='` + userID + `'
|
||||
AND Deleted = 0`
|
||||
}
|
||||
|
||||
// DeleteTripPointsForTripQuery - REQUIRED tripID
|
||||
func DeleteTripPointsForTripQuery(tripID string) string {
|
||||
return fmt.Sprintf("UPDATE TripPoints SET Deleted = 1 WHERE TripId = '%s'", tripID)
|
||||
}
|
||||
|
||||
// DeleteTripQuery - REQUIRED tripID
|
||||
func DeleteTripQuery(tripID string) string {
|
||||
return fmt.Sprintf("UPDAte Trips SET Deleted = 1 WHERE Id = '%s'", tripID)
|
||||
}
|
||||
|
||||
// UpdateTripQuery - REQUIRED trip object and tripID
|
||||
func UpdateTripQuery(trip Trip) string {
|
||||
var query = `UPDATE Trips SET
|
||||
Name = '%s',
|
||||
UserId = '%s',
|
||||
RecordedTimeStamp = '%s',
|
||||
EndTimeStamp = '%s',
|
||||
Rating = %d,
|
||||
IsComplete = '%s',
|
||||
HasSimulatedOBDData = '%s',
|
||||
AverageSpeed = %g,
|
||||
FuelUsed = %g,
|
||||
HardStops = %d,
|
||||
HardAccelerations = %d,
|
||||
Distance = %g,
|
||||
UpdatedAt = GETDATE()
|
||||
WHERE Id = '%s'`
|
||||
|
||||
var formattedQuery = fmt.Sprintf(
|
||||
query,
|
||||
trip.Name,
|
||||
trip.UserID,
|
||||
trip.RecordedTimeStamp,
|
||||
trip.EndTimeStamp,
|
||||
trip.Rating,
|
||||
strconv.FormatBool(trip.IsComplete),
|
||||
strconv.FormatBool(trip.HasSimulatedOBDData),
|
||||
trip.AverageSpeed,
|
||||
trip.FuelUsed,
|
||||
trip.HardStops,
|
||||
trip.HardAccelerations,
|
||||
trip.Distance,
|
||||
trip.ID)
|
||||
|
||||
Debug.Println("updateTripQuery: " + formattedQuery)
|
||||
|
||||
return formattedQuery
|
||||
}
|
||||
|
||||
func createTripQuery(trip Trip) string {
|
||||
var query = `DECLARE @tempReturn
|
||||
TABLE (TripId NVARCHAR(128));
|
||||
INSERT INTO Trips (
|
||||
Name,
|
||||
UserId,
|
||||
RecordedTimeStamp,
|
||||
EndTimeStamp,
|
||||
Rating,
|
||||
IsComplete,
|
||||
HasSimulatedOBDData,
|
||||
AverageSpeed,
|
||||
FuelUsed,
|
||||
HardStops,
|
||||
HardAccelerations,
|
||||
Distance,
|
||||
UpdatedAt,
|
||||
Deleted)
|
||||
OUTPUT Inserted.ID
|
||||
INTO @tempReturn
|
||||
VALUES (
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
%d,
|
||||
'%s',
|
||||
'%s',
|
||||
%g,
|
||||
%g,
|
||||
%d,
|
||||
%d,
|
||||
%g,
|
||||
GETDATE(),
|
||||
'false');
|
||||
SELECT TripId FROM @tempReturn`
|
||||
|
||||
var formattedQuery = fmt.Sprintf(
|
||||
query,
|
||||
trip.Name,
|
||||
trip.UserID,
|
||||
trip.RecordedTimeStamp,
|
||||
trip.EndTimeStamp,
|
||||
trip.Rating,
|
||||
strconv.FormatBool(trip.IsComplete),
|
||||
strconv.FormatBool(trip.HasSimulatedOBDData),
|
||||
trip.AverageSpeed,
|
||||
trip.FuelUsed,
|
||||
trip.HardStops,
|
||||
trip.HardAccelerations,
|
||||
trip.Distance)
|
||||
|
||||
Debug.Println("createTripQuery: " + formattedQuery)
|
||||
|
||||
return formattedQuery
|
||||
}
|
||||
|
||||
func selectTripPointsForTripQuery(tripID string) string {
|
||||
|
||||
var query = `SELECT
|
||||
[Id],
|
||||
[TripId],
|
||||
[Latitude],
|
||||
[Longitude],
|
||||
[Speed],
|
||||
[RecordedTimeStamp],
|
||||
[Sequence],
|
||||
[RPM],
|
||||
[ShortTermFuelBank],
|
||||
[LongTermFuelBank],
|
||||
[ThrottlePosition],
|
||||
[RelativeThrottlePosition],
|
||||
[Runtime],
|
||||
[DistanceWithMalfunctionLight],
|
||||
[EngineLoad],
|
||||
[EngineFuelRate],
|
||||
[VIN]
|
||||
FROM [dbo].[TripPoints]
|
||||
WHERE
|
||||
TripId = '%s'
|
||||
AND Deleted = 0`
|
||||
|
||||
var formattedQuery = fmt.Sprintf(
|
||||
query,
|
||||
tripID)
|
||||
|
||||
Debug.Println("selectTripPointsForTripQuery: " + formattedQuery)
|
||||
|
||||
return formattedQuery
|
||||
}
|
||||
|
||||
func selectTripPointsForTripPointIDQuery(tripPointID string) string {
|
||||
var query = `SELECT
|
||||
[Id],
|
||||
[TripId],
|
||||
[Latitude],
|
||||
[Longitude],
|
||||
[Speed],
|
||||
[RecordedTimeStamp],
|
||||
[Sequence],
|
||||
[RPM],
|
||||
[ShortTermFuelBank],
|
||||
[LongTermFuelBank],
|
||||
[ThrottlePosition],
|
||||
[RelativeThrottlePosition],
|
||||
[Runtime],
|
||||
[DistanceWithMalfunctionLight],
|
||||
[EngineLoad],
|
||||
[EngineFuelRate],
|
||||
[VIN]
|
||||
FROM TripPoints
|
||||
WHERE Id = '%s'
|
||||
AND Deleted = 0`
|
||||
|
||||
var formattedQuery = fmt.Sprintf(
|
||||
query,
|
||||
tripPointID)
|
||||
|
||||
Debug.Println("selectTripPointsForTripPointIDQuery: " + formattedQuery)
|
||||
|
||||
return formattedQuery
|
||||
}
|
||||
|
||||
func createTripPointQuery(tripPoint TripPoint, tripID string) string {
|
||||
var query = `DECLARE @tempReturn TABLE (TripPointId NVARCHAR(128));
|
||||
INSERT INTO TripPoints (
|
||||
[TripId],
|
||||
[Latitude],
|
||||
[Longitude],
|
||||
[Speed],
|
||||
[RecordedTimeStamp],
|
||||
[Sequence],
|
||||
[RPM],
|
||||
[ShortTermFuelBank],
|
||||
[LongTermFuelBank],
|
||||
[ThrottlePosition],
|
||||
[RelativeThrottlePosition],
|
||||
[Runtime],
|
||||
[DistanceWithMalfunctionLight],
|
||||
[EngineLoad],
|
||||
[EngineFuelRate],
|
||||
[MassFlowRate],
|
||||
[HasOBDData],
|
||||
[HasSimulatedOBDData],
|
||||
[VIN],
|
||||
[UpdatedAt],
|
||||
[Deleted])
|
||||
OUTPUT
|
||||
Inserted.ID
|
||||
INTO @tempReturn
|
||||
VALUES (
|
||||
'%s',
|
||||
%g,
|
||||
%g,
|
||||
%g,
|
||||
'%s',
|
||||
%d,
|
||||
%g,
|
||||
%g,
|
||||
%g,
|
||||
%g,
|
||||
%g,
|
||||
%g,
|
||||
%g,
|
||||
%g,
|
||||
%g,
|
||||
%g,
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
GETDATE(),
|
||||
'false');
|
||||
SELECT TripPointId
|
||||
FROM @tempReturn`
|
||||
|
||||
var formattedQuery = fmt.Sprintf(
|
||||
query,
|
||||
tripID,
|
||||
tripPoint.Latitude,
|
||||
tripPoint.Longitude,
|
||||
tripPoint.Speed,
|
||||
tripPoint.RecordedTimeStamp,
|
||||
tripPoint.Sequence,
|
||||
tripPoint.RPM,
|
||||
tripPoint.ShortTermFuelBank,
|
||||
tripPoint.LongTermFuelBank,
|
||||
tripPoint.ThrottlePosition,
|
||||
tripPoint.RelativeThrottlePosition,
|
||||
tripPoint.Runtime,
|
||||
tripPoint.DistanceWithMalfunctionLight,
|
||||
tripPoint.EngineLoad,
|
||||
tripPoint.MassFlowRate,
|
||||
tripPoint.EngineFuelRate,
|
||||
strconv.FormatBool(tripPoint.HasOBDData),
|
||||
strconv.FormatBool(tripPoint.HasSimulatedOBDData),
|
||||
tripPoint.VIN)
|
||||
|
||||
Debug.Println("createTripPointQuery: " + formattedQuery)
|
||||
|
||||
return formattedQuery
|
||||
}
|
||||
|
||||
func updateTripPointQuery(tripPoint TripPoint) string {
|
||||
var query = `UPDATE [TripPoints]
|
||||
SET [TripId] = '%s',
|
||||
[Latitude] = '%s',
|
||||
[Longitude] = '%s',
|
||||
[Speed] = '%s',
|
||||
[RecordedTimeStamp] = '%s',
|
||||
[Sequence] = %d,[RPM] = '%s',
|
||||
[ShortTermFuelBank] = '%s',
|
||||
[LongTermFuelBank] = '%s',
|
||||
[ThrottlePosition] = '%s',
|
||||
[RelativeThrottlePosition] = '%s',
|
||||
[Runtime] = '%s',
|
||||
[DistanceWithMalfunctionLight] = '%s',
|
||||
[EngineLoad] = '%s',
|
||||
[MassFlowRate] = '%s',
|
||||
[EngineFuelRate] = '%s',
|
||||
[HasOBDData] = '%s',
|
||||
[HasSimulatedOBDData] = '%s',
|
||||
[VIN] = '%s'
|
||||
WHERE Id = '%s'`
|
||||
|
||||
var formattedQuery = fmt.Sprintf(
|
||||
query,
|
||||
tripPoint.TripID,
|
||||
tripPoint.Latitude,
|
||||
tripPoint.Longitude,
|
||||
tripPoint.Speed,
|
||||
tripPoint.RecordedTimeStamp,
|
||||
tripPoint.Sequence,
|
||||
tripPoint.RPM,
|
||||
tripPoint.ShortTermFuelBank,
|
||||
tripPoint.LongTermFuelBank,
|
||||
tripPoint.ThrottlePosition,
|
||||
tripPoint.RelativeThrottlePosition,
|
||||
tripPoint.Runtime,
|
||||
tripPoint.DistanceWithMalfunctionLight,
|
||||
tripPoint.EngineLoad,
|
||||
tripPoint.MassFlowRate,
|
||||
tripPoint.EngineFuelRate,
|
||||
strconv.FormatBool(tripPoint.HasOBDData),
|
||||
strconv.FormatBool(tripPoint.HasSimulatedOBDData),
|
||||
tripPoint.VIN,
|
||||
tripPoint.ID)
|
||||
|
||||
Debug.Println("updateTripPointQuery: " + formattedQuery)
|
||||
|
||||
return formattedQuery
|
||||
}
|
||||
|
||||
func deleteTripPointQuery(tripPointID string) string {
|
||||
var query = `UPDATE TripPoints
|
||||
SET Deleted = 1
|
||||
WHERE Id = '%s'`
|
||||
|
||||
var formattedQuery = fmt.Sprintf(
|
||||
query,
|
||||
tripPointID)
|
||||
|
||||
Debug.Println("deleteTripPointQuery: " + formattedQuery)
|
||||
|
||||
return formattedQuery
|
||||
}
|
416
apis/trips/tripsgo/queries_test.go
Normal file
416
apis/trips/tripsgo/queries_test.go
Normal file
@ -0,0 +1,416 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUnitdeleteTripPointQuery(t *testing.T) {
|
||||
//arrange
|
||||
var expected = `UPDATE TripPoints
|
||||
SET Deleted = 1
|
||||
WHERE Id = '1234'`
|
||||
//act
|
||||
query := deleteTripPointQuery("1234")
|
||||
//assert
|
||||
if query != expected {
|
||||
t.Errorf("Error \nExpected: %s \nGot: %s", expected, query)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnitupdateTripPointQuery(t *testing.T) {
|
||||
//arrange
|
||||
tripPoint := TripPoint{
|
||||
ID: "abcd",
|
||||
TripID: "a_trip",
|
||||
Latitude: 51.5244282,
|
||||
Longitude: -0.0784379,
|
||||
Speed: 185.2,
|
||||
RecordedTimeStamp: "a_timestamp",
|
||||
Sequence: 1,
|
||||
RPM: 4000,
|
||||
ShortTermFuelBank: 1,
|
||||
LongTermFuelBank: 2,
|
||||
ThrottlePosition: 3,
|
||||
RelativeThrottlePosition: 4,
|
||||
Runtime: 5,
|
||||
DistanceWithMalfunctionLight: 6,
|
||||
EngineLoad: 7,
|
||||
MassFlowRate: 8,
|
||||
EngineFuelRate: 9,
|
||||
HasOBDData: true,
|
||||
HasSimulatedOBDData: false,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
Deleted: false,
|
||||
}
|
||||
|
||||
var expected = `UPDATE [TripPoints]
|
||||
SET [TripId] = 'a_trip',
|
||||
[Latitude] = '%!s(float32=51.52443)',
|
||||
[Longitude] = '%!s(float32=-0.0784379)',
|
||||
[Speed] = '%!s(float32=185.2)',
|
||||
[RecordedTimeStamp] = 'a_timestamp',
|
||||
[Sequence] = 1,[RPM] = '%!s(float32=4000)',
|
||||
[ShortTermFuelBank] = '%!s(float32=1)',
|
||||
[LongTermFuelBank] = '%!s(float32=2)',
|
||||
[ThrottlePosition] = '%!s(float32=3)',
|
||||
[RelativeThrottlePosition] = '%!s(float32=4)',
|
||||
[Runtime] = '%!s(float32=5)',
|
||||
[DistanceWithMalfunctionLight] = '%!s(float32=6)',
|
||||
[EngineLoad] = '%!s(float32=7)',
|
||||
[MassFlowRate] = '%!s(float32=8)',
|
||||
[EngineFuelRate] = '%!s(float32=9)',
|
||||
[HasOBDData] = 'true',
|
||||
[HasSimulatedOBDData] = 'false',
|
||||
[VIN] = '{ %!s(bool=false)}'
|
||||
WHERE Id = 'abcd'`
|
||||
//act
|
||||
query := updateTripPointQuery(tripPoint)
|
||||
//assert
|
||||
assert.Equal(t, expected, query)
|
||||
}
|
||||
|
||||
func TestSelectAllTripsQueryUnit(t *testing.T) {
|
||||
//arrange
|
||||
var expected = `SELECT
|
||||
Id,
|
||||
Name,
|
||||
UserId,
|
||||
RecordedTimeStamp,
|
||||
EndTimeStamp,
|
||||
Rating,
|
||||
IsComplete,
|
||||
HasSimulatedOBDData,
|
||||
AverageSpeed,
|
||||
FuelUsed,
|
||||
HardStops,
|
||||
HardAccelerations,
|
||||
Distance,
|
||||
CreatedAt,
|
||||
UpdatedAt
|
||||
FROM Trips
|
||||
WHERE Deleted = 0`
|
||||
//act
|
||||
query := SelectAllTripsQuery()
|
||||
//assert
|
||||
assert.Equal(t, expected, query)
|
||||
}
|
||||
|
||||
func TestSelectAllTripsForUserQueryUnit(t *testing.T) {
|
||||
//arrange
|
||||
var expected = `SELECT
|
||||
Id,
|
||||
Name,
|
||||
UserId,
|
||||
RecordedTimeStamp,
|
||||
EndTimeStamp,
|
||||
Rating,
|
||||
IsComplete,
|
||||
HasSimulatedOBDData,
|
||||
AverageSpeed,
|
||||
FuelUsed,
|
||||
HardStops,
|
||||
HardAccelerations,
|
||||
Distance,
|
||||
CreatedAt,
|
||||
UpdatedAt
|
||||
FROM Trips
|
||||
WHERE UserId ='fake_user'
|
||||
AND Deleted = 0`
|
||||
//act
|
||||
query := SelectAllTripsForUserQuery("fake_user")
|
||||
//assert
|
||||
assert.Equal(t, expected, query)
|
||||
}
|
||||
|
||||
func TestDeleteTripPointsForTripQueryUnit(t *testing.T) {
|
||||
//arrange
|
||||
var expected = `UPDATE TripPoints SET Deleted = 1 WHERE TripId = 'trip_123'`
|
||||
//act
|
||||
query := DeleteTripPointsForTripQuery("trip_123")
|
||||
//assert
|
||||
assert.Equal(t, expected, query)
|
||||
}
|
||||
|
||||
func TestDeleteTripQueryUnit(t *testing.T) {
|
||||
//arrange
|
||||
var expected = `UPDAte Trips SET Deleted = 1 WHERE Id = 'trip_123'`
|
||||
//act
|
||||
query := DeleteTripQuery("trip_123")
|
||||
//assert
|
||||
assert.Equal(t, expected, query)
|
||||
}
|
||||
|
||||
func TestSelectTripByIDQueryUnit(t *testing.T) {
|
||||
//arrange
|
||||
var expected = `SELECT
|
||||
Id,
|
||||
Name,
|
||||
UserId,
|
||||
RecordedTimeStamp,
|
||||
EndTimeStamp,
|
||||
Rating,
|
||||
IsComplete,
|
||||
HasSimulatedOBDData,
|
||||
AverageSpeed,
|
||||
FuelUsed,
|
||||
HardStops,
|
||||
HardAccelerations,
|
||||
Distance,
|
||||
CreatedAt,
|
||||
UpdatedAt
|
||||
FROM Trips
|
||||
WHERE Id = 'trip_123'
|
||||
AND Deleted = 0`
|
||||
//act
|
||||
query := SelectTripByIDQuery("trip_123")
|
||||
//assert
|
||||
assert.Equal(t, expected, query)
|
||||
}
|
||||
|
||||
func TestSelectTripPointsForTripPointIDQueryUnit(t *testing.T) {
|
||||
//arrange
|
||||
var expected = `SELECT
|
||||
[Id],
|
||||
[TripId],
|
||||
[Latitude],
|
||||
[Longitude],
|
||||
[Speed],
|
||||
[RecordedTimeStamp],
|
||||
[Sequence],
|
||||
[RPM],
|
||||
[ShortTermFuelBank],
|
||||
[LongTermFuelBank],
|
||||
[ThrottlePosition],
|
||||
[RelativeThrottlePosition],
|
||||
[Runtime],
|
||||
[DistanceWithMalfunctionLight],
|
||||
[EngineLoad],
|
||||
[EngineFuelRate],
|
||||
[VIN]
|
||||
FROM TripPoints
|
||||
WHERE Id = 'point_ab'
|
||||
AND Deleted = 0`
|
||||
//act
|
||||
query := selectTripPointsForTripPointIDQuery("point_ab")
|
||||
//assert
|
||||
assert.Equal(t, expected, query)
|
||||
}
|
||||
|
||||
func TestUpdateTripQueryUnit(t *testing.T) {
|
||||
//arrange
|
||||
trip := Trip{
|
||||
ID: "abcd",
|
||||
Name: "fake Trip",
|
||||
UserID: "fake user",
|
||||
RecordedTimeStamp: "now",
|
||||
EndTimeStamp: "then",
|
||||
Rating: 1,
|
||||
IsComplete: false,
|
||||
HasSimulatedOBDData: false,
|
||||
AverageSpeed: 88,
|
||||
FuelUsed: 23.2,
|
||||
HardStops: 8,
|
||||
HardAccelerations: 12,
|
||||
Distance: 5,
|
||||
Created: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
Deleted: false,
|
||||
}
|
||||
var expected = `UPDATE Trips SET
|
||||
Name = 'fake Trip',
|
||||
UserId = 'fake user',
|
||||
RecordedTimeStamp = 'now',
|
||||
EndTimeStamp = 'then',
|
||||
Rating = 1,
|
||||
IsComplete = 'false',
|
||||
HasSimulatedOBDData = 'false',
|
||||
AverageSpeed = 88,
|
||||
FuelUsed = 23.2,
|
||||
HardStops = 8,
|
||||
HardAccelerations = 12,
|
||||
Distance = 5,
|
||||
UpdatedAt = GETDATE()
|
||||
WHERE Id = 'abcd'`
|
||||
//act
|
||||
query := UpdateTripQuery(trip)
|
||||
//assert
|
||||
assert.Equal(t, expected, query)
|
||||
}
|
||||
|
||||
func TestCreateTripQueryUnit(t *testing.T) {
|
||||
//arrange
|
||||
trip := Trip{
|
||||
ID: "abcd",
|
||||
Name: "fake Trip",
|
||||
UserID: "fake user",
|
||||
RecordedTimeStamp: "now",
|
||||
EndTimeStamp: "then",
|
||||
Rating: 1,
|
||||
IsComplete: false,
|
||||
HasSimulatedOBDData: false,
|
||||
AverageSpeed: 88,
|
||||
FuelUsed: 23.2,
|
||||
HardStops: 8,
|
||||
HardAccelerations: 12,
|
||||
Distance: 5,
|
||||
Created: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
Deleted: false,
|
||||
}
|
||||
var expected = `DECLARE @tempReturn
|
||||
TABLE (TripId NVARCHAR(128));
|
||||
INSERT INTO Trips (
|
||||
Name,
|
||||
UserId,
|
||||
RecordedTimeStamp,
|
||||
EndTimeStamp,
|
||||
Rating,
|
||||
IsComplete,
|
||||
HasSimulatedOBDData,
|
||||
AverageSpeed,
|
||||
FuelUsed,
|
||||
HardStops,
|
||||
HardAccelerations,
|
||||
Distance,
|
||||
UpdatedAt,
|
||||
Deleted)
|
||||
OUTPUT Inserted.ID
|
||||
INTO @tempReturn
|
||||
VALUES (
|
||||
'fake Trip',
|
||||
'fake user',
|
||||
'now',
|
||||
'then',
|
||||
1,
|
||||
'false',
|
||||
'false',
|
||||
88,
|
||||
23.2,
|
||||
8,
|
||||
12,
|
||||
5,
|
||||
GETDATE(),
|
||||
'false');
|
||||
SELECT TripId FROM @tempReturn`
|
||||
//act
|
||||
query := createTripQuery(trip)
|
||||
//assert
|
||||
assert.Equal(t, expected, query)
|
||||
}
|
||||
|
||||
func TestSelectTripPointsForTripQueryUnit(t *testing.T) {
|
||||
//arrange
|
||||
var expected = `SELECT
|
||||
[Id],
|
||||
[TripId],
|
||||
[Latitude],
|
||||
[Longitude],
|
||||
[Speed],
|
||||
[RecordedTimeStamp],
|
||||
[Sequence],
|
||||
[RPM],
|
||||
[ShortTermFuelBank],
|
||||
[LongTermFuelBank],
|
||||
[ThrottlePosition],
|
||||
[RelativeThrottlePosition],
|
||||
[Runtime],
|
||||
[DistanceWithMalfunctionLight],
|
||||
[EngineLoad],
|
||||
[EngineFuelRate],
|
||||
[VIN]
|
||||
FROM [dbo].[TripPoints]
|
||||
WHERE
|
||||
TripId = 'trip_zzyzx'
|
||||
AND Deleted = 0`
|
||||
//act
|
||||
query := selectTripPointsForTripQuery("trip_zzyzx")
|
||||
//assert
|
||||
assert.Equal(t, expected, query)
|
||||
}
|
||||
|
||||
func TestCreateTripPointQueryUnit(t *testing.T) {
|
||||
//arrange
|
||||
tripPoint := TripPoint{
|
||||
ID: "abcd",
|
||||
TripID: "a_trip",
|
||||
Latitude: 51.5244282,
|
||||
Longitude: -0.0784379,
|
||||
Speed: 185.2,
|
||||
RecordedTimeStamp: "a_timestamp",
|
||||
Sequence: 1,
|
||||
RPM: 4000,
|
||||
ShortTermFuelBank: 1,
|
||||
LongTermFuelBank: 2,
|
||||
ThrottlePosition: 3,
|
||||
RelativeThrottlePosition: 4,
|
||||
Runtime: 5,
|
||||
DistanceWithMalfunctionLight: 6,
|
||||
EngineLoad: 7,
|
||||
MassFlowRate: 8,
|
||||
EngineFuelRate: 9,
|
||||
HasOBDData: true,
|
||||
HasSimulatedOBDData: false,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
Deleted: false,
|
||||
}
|
||||
|
||||
var expected = `DECLARE @tempReturn TABLE (TripPointId NVARCHAR(128));
|
||||
INSERT INTO TripPoints (
|
||||
[TripId],
|
||||
[Latitude],
|
||||
[Longitude],
|
||||
[Speed],
|
||||
[RecordedTimeStamp],
|
||||
[Sequence],
|
||||
[RPM],
|
||||
[ShortTermFuelBank],
|
||||
[LongTermFuelBank],
|
||||
[ThrottlePosition],
|
||||
[RelativeThrottlePosition],
|
||||
[Runtime],
|
||||
[DistanceWithMalfunctionLight],
|
||||
[EngineLoad],
|
||||
[EngineFuelRate],
|
||||
[MassFlowRate],
|
||||
[HasOBDData],
|
||||
[HasSimulatedOBDData],
|
||||
[VIN],
|
||||
[UpdatedAt],
|
||||
[Deleted])
|
||||
OUTPUT
|
||||
Inserted.ID
|
||||
INTO @tempReturn
|
||||
VALUES (
|
||||
'fake_trip_id',
|
||||
51.52443,
|
||||
-0.0784379,
|
||||
185.2,
|
||||
'a_timestamp',
|
||||
1,
|
||||
4000,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
'true',
|
||||
'false',
|
||||
'{ %!s(bool=false)}',
|
||||
GETDATE(),
|
||||
'false');
|
||||
SELECT TripPointId
|
||||
FROM @tempReturn`
|
||||
//act
|
||||
query := createTripPointQuery(tripPoint, "fake_trip_id")
|
||||
//assert
|
||||
assert.Equal(t, expected, query)
|
||||
}
|
203
apis/trips/tripsgo/routers.go
Normal file
203
apis/trips/tripsgo/routers.go
Normal file
@ -0,0 +1,203 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/codemodus/swagui"
|
||||
"github.com/codemodus/swagui/suidata3"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var (
|
||||
du = flag.String("du", getEnv("DOCS_URI", "http://localhost:8080"), "docs endpoint")
|
||||
wsbu = flag.String("wsbu", getEnv("WEB_SERVER_BASE_URI", "changeme"), "base portion of server uri")
|
||||
)
|
||||
|
||||
// Route - object representing a route handler
|
||||
type Route struct {
|
||||
Name string
|
||||
Method string
|
||||
Pattern string
|
||||
HandlerFunc http.HandlerFunc
|
||||
}
|
||||
|
||||
// Routes - Route handler collection
|
||||
type Routes []Route
|
||||
|
||||
// NewRouter - Constructor
|
||||
func NewRouter() *mux.Router {
|
||||
router := mux.NewRouter().StrictSlash(true)
|
||||
for _, route := range routes {
|
||||
CreateHandler(router, route)
|
||||
}
|
||||
|
||||
// add docs route
|
||||
CreateDocsHandler(router, docsRoute)
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
// Index - Default route handler for service base uri
|
||||
func Index(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Trips Service")
|
||||
}
|
||||
|
||||
// CreateHandler - Create router handler
|
||||
func CreateHandler(router *mux.Router, route Route) {
|
||||
var handler http.Handler
|
||||
handler = route.HandlerFunc
|
||||
handler = Logger(handler, route.Name)
|
||||
|
||||
router.
|
||||
Methods(route.Method).
|
||||
Path(route.Pattern).
|
||||
Name(route.Name).
|
||||
Handler(handler)
|
||||
}
|
||||
|
||||
// CreateDocsHandler - Create route handler for docs using SwagUI
|
||||
func CreateDocsHandler(router *mux.Router, route Route) {
|
||||
var def = "/api/json/swagger.json"
|
||||
|
||||
var provider = suidata3.New()
|
||||
|
||||
ui, err := swagui.New(http.NotFoundHandler(), provider)
|
||||
if err != nil {
|
||||
Info.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
router.
|
||||
Methods(route.Method).
|
||||
Name(route.Name).
|
||||
Handler(ui.Handler(def))
|
||||
|
||||
router.
|
||||
Methods(route.Method).
|
||||
Path("/api/docs/trips/{dir}/{fileName}").
|
||||
Name("*").
|
||||
Handler(ui.Handler(def))
|
||||
|
||||
router.
|
||||
Methods(route.Method).
|
||||
Path("/api/docs/trips/{fileName}").
|
||||
Name("Swagger UI JS").
|
||||
Handler(ui.Handler(def))
|
||||
}
|
||||
|
||||
var docsRoute = Route{
|
||||
"swagger-ui",
|
||||
"GET",
|
||||
"/api/docs/trips/",
|
||||
nil,
|
||||
}
|
||||
|
||||
var routes = Routes{
|
||||
Route{
|
||||
"Index",
|
||||
"GET",
|
||||
"/api/",
|
||||
Index,
|
||||
},
|
||||
|
||||
Route{
|
||||
"swagger-json",
|
||||
"GET",
|
||||
"/api/json/swagger.json",
|
||||
swaggerDocsJSON,
|
||||
},
|
||||
|
||||
Route{
|
||||
"CreateTrip",
|
||||
"POST",
|
||||
"/api/trips",
|
||||
createTrip,
|
||||
},
|
||||
|
||||
Route{
|
||||
"CreateTripPoint",
|
||||
"POST",
|
||||
"/api/trips/{tripID}/trippoints",
|
||||
createTripPoint,
|
||||
},
|
||||
|
||||
Route{
|
||||
"DeleteTrip",
|
||||
"DELETE",
|
||||
"/api/trips/{tripID}",
|
||||
deleteTrip,
|
||||
},
|
||||
|
||||
Route{
|
||||
"DeleteTripPoint",
|
||||
"DELETE",
|
||||
"/api/trips/{tripID}/trippoints/{tripPointID}",
|
||||
deleteTripPoint,
|
||||
},
|
||||
|
||||
Route{
|
||||
"GetAllTrips",
|
||||
"GET",
|
||||
"/api/trips",
|
||||
getAllTrips,
|
||||
},
|
||||
|
||||
Route{
|
||||
"GetAllTripsForUser",
|
||||
"GET",
|
||||
"/api/trips/user/{userID}",
|
||||
getAllTripsForUser,
|
||||
},
|
||||
|
||||
Route{
|
||||
"GetTripById",
|
||||
"GET",
|
||||
"/api/trips/{tripID}",
|
||||
getTripByID,
|
||||
},
|
||||
|
||||
Route{
|
||||
"GetTripPointByID",
|
||||
"GET",
|
||||
"/api/trips/{tripID}/trippoints/{tripPointID}",
|
||||
getTripPointByID,
|
||||
},
|
||||
|
||||
Route{
|
||||
"GetTripPoints",
|
||||
"GET",
|
||||
"/api/trips/{tripID}/trippoints",
|
||||
getTripPoints,
|
||||
},
|
||||
|
||||
Route{
|
||||
"HealthcheckGet",
|
||||
"GET",
|
||||
"/api/healthcheck/trips",
|
||||
healthcheckGet,
|
||||
},
|
||||
|
||||
Route{
|
||||
"UpdateTrip",
|
||||
"PATCH",
|
||||
"/api/trips/{tripID}",
|
||||
updateTrip,
|
||||
},
|
||||
|
||||
Route{
|
||||
"UpdateTripPoint",
|
||||
"PATCH",
|
||||
"/api/trips/{tripID}/trippoints/{tripPointID}",
|
||||
updateTripPoint,
|
||||
},
|
||||
|
||||
Route{
|
||||
"VersionGet",
|
||||
"GET",
|
||||
"/api/version/trips",
|
||||
versionGet,
|
||||
},
|
||||
}
|
28
apis/trips/tripsgo/swaggerService.go
Normal file
28
apis/trips/tripsgo/swaggerService.go
Normal file
@ -0,0 +1,28 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getSwaggerJsonPath() string {
|
||||
if value, ok := os.LookupEnv("SWAGGER_JSON_PATH"); ok {
|
||||
return value
|
||||
}
|
||||
return "./api/swagger.json"
|
||||
}
|
||||
|
||||
func swaggerDocsJSON(w http.ResponseWriter, r *http.Request) {
|
||||
swaggerPath := getSwaggerJsonPath()
|
||||
fData, err := os.Open(swaggerPath)
|
||||
if err != nil {
|
||||
var msg = fmt.Sprintf("swaggerDocsJson - Unable to open and read swagger.json : %v", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
Info.Println(msg)
|
||||
http.Error(w, msg, -1)
|
||||
return
|
||||
}
|
||||
http.ServeContent(w, r, "swagger.json", time.Now(), fData)
|
||||
}
|
49
apis/trips/tripsgo/swaggerService_test.go
Normal file
49
apis/trips/tripsgo/swaggerService_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSwaggerServiceSuccessUnit(t *testing.T) {
|
||||
router := NewRouter()
|
||||
os.Setenv("SWAGGER_JSON_PATH", "../api/swagger.json")
|
||||
var debug, present = os.LookupEnv("DEBUG_LOGGING")
|
||||
|
||||
if present && debug == "true" {
|
||||
InitLogging(os.Stdout, os.Stdout, os.Stdout)
|
||||
} else {
|
||||
// if debug env is not present or false, do not log debug output to console
|
||||
InitLogging(os.Stdout, ioutil.Discard, os.Stdout)
|
||||
}
|
||||
RunAPITests(t, router, []APITestCase{
|
||||
{
|
||||
Tag: "swaggerService",
|
||||
Method: "GET",
|
||||
URL: "/api/json/swagger.json",
|
||||
Status: 200,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestSwaggerServiceFailUnit(t *testing.T) {
|
||||
router := NewRouter()
|
||||
os.Unsetenv("SWAGGER_JSON_PATH")
|
||||
var debug, present = os.LookupEnv("DEBUG_LOGGING")
|
||||
|
||||
if present && debug == "true" {
|
||||
InitLogging(os.Stdout, os.Stdout, os.Stdout)
|
||||
} else {
|
||||
// if debug env is not present or false, do not log debug output to console
|
||||
InitLogging(os.Stdout, ioutil.Discard, os.Stdout)
|
||||
}
|
||||
RunAPITestsPlainText(t, router, []APITestCase{
|
||||
{
|
||||
Tag: "swaggerService",
|
||||
Method: "GET",
|
||||
URL: "/api/json/swagger.json",
|
||||
Status: 500,
|
||||
},
|
||||
})
|
||||
}
|
66
apis/trips/tripsgo/test_util.go
Normal file
66
apis/trips/tripsgo/test_util.go
Normal file
@ -0,0 +1,66 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// APITestCase needs to be exported to be accessed for test dir
|
||||
type APITestCase struct {
|
||||
Tag string
|
||||
Method string
|
||||
URL string
|
||||
Body string
|
||||
Status int
|
||||
ExpectedResponse string
|
||||
ActualResponse string
|
||||
}
|
||||
|
||||
func testAPI(router *mux.Router, method, URL, body string) *httptest.ResponseRecorder {
|
||||
req, _ := http.NewRequest(method, URL, bytes.NewBufferString(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
res := httptest.NewRecorder()
|
||||
router.ServeHTTP(res, req)
|
||||
return res
|
||||
}
|
||||
|
||||
// RunAPITests needs to be exported to be accessed for test dir
|
||||
func RunAPITests(t *testing.T, router *mux.Router, tests []APITestCase) {
|
||||
for i := 0; i < len(tests); i++ {
|
||||
res := testAPI(router, tests[i].Method, tests[i].URL, tests[i].Body)
|
||||
tests[i].ActualResponse = res.Body.String()
|
||||
Debug.Println(tests[i].Tag + " - " + tests[i].ActualResponse)
|
||||
assert.Equal(t, tests[i].Status, res.Code, tests[i].Tag)
|
||||
Info.Println(tests[i].Tag + "- Response Code:" + strconv.Itoa(res.Code))
|
||||
if tests[i].ExpectedResponse != "" {
|
||||
assert.JSONEq(t, tests[i].ExpectedResponse, res.Body.String(), tests[i].Tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RunAPITestsPlainText(t *testing.T, router *mux.Router, tests []APITestCase) {
|
||||
for i := 0; i < len(tests); i++ {
|
||||
res := testAPI(router, tests[i].Method, tests[i].URL, tests[i].Body)
|
||||
tests[i].ActualResponse = res.Body.String()
|
||||
Debug.Println(tests[i].Tag + " - " + tests[i].ActualResponse)
|
||||
assert.Equal(t, tests[i].Status, res.Code, tests[i].Tag)
|
||||
Info.Println(tests[i].Tag + "- Response Code:" + strconv.Itoa(res.Code))
|
||||
if tests[i].ExpectedResponse != "" {
|
||||
assert.Equal(t, tests[i].ExpectedResponse, res.Body.String(), tests[i].Tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resetDataAccessEnvVars() {
|
||||
var fls bool = false
|
||||
debug = &fls
|
||||
godotenv.Overload()
|
||||
RebindDataAccessEnvironmentVariables()
|
||||
}
|
43
apis/trips/tripsgo/trip.go
Normal file
43
apis/trips/tripsgo/trip.go
Normal file
@ -0,0 +1,43 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Trip - Represents a single trip by a user with its associated set of trip points.
|
||||
type Trip struct {
|
||||
|
||||
// Trip ID
|
||||
ID string `json:"Id"`
|
||||
|
||||
Name string `json:"Name"`
|
||||
|
||||
// User's unique identity
|
||||
UserID string `json:"UserId"`
|
||||
|
||||
RecordedTimeStamp string `json:"RecordedTimeStamp"`
|
||||
|
||||
EndTimeStamp string `json:"EndTimeStamp"`
|
||||
|
||||
Rating int32 `json:"Rating"`
|
||||
|
||||
IsComplete bool `json:"IsComplete"`
|
||||
|
||||
HasSimulatedOBDData bool `json:"HasSimulatedOBDData"`
|
||||
|
||||
AverageSpeed float32 `json:"AverageSpeed"`
|
||||
|
||||
FuelUsed float32 `json:"FuelUsed"`
|
||||
|
||||
HardStops int64 `json:"HardStops"`
|
||||
|
||||
HardAccelerations int64 `json:"HardAccelerations"`
|
||||
|
||||
Distance float32 `json:"Distance"`
|
||||
|
||||
Created time.Time `json:"Created"`
|
||||
|
||||
UpdatedAt time.Time `json:"UpdatedAt"`
|
||||
|
||||
Deleted bool `json:"Deleted,omitempty"`
|
||||
}
|
258
apis/trips/tripsgo/tripPointService.go
Normal file
258
apis/trips/tripsgo/tripPointService.go
Normal file
@ -0,0 +1,258 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// TripPoint Service Methods
|
||||
|
||||
func getTripPoints(w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
|
||||
var tripID = params["tripID"]
|
||||
|
||||
var query = selectTripPointsForTripQuery(tripID)
|
||||
|
||||
statement, err := ExecuteQuery(query)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Error while retrieving trip points from database"
|
||||
logError(err, msg)
|
||||
fmt.Fprintf(w, SerializeError(err, msg))
|
||||
return
|
||||
}
|
||||
|
||||
tripPointRows := []TripPoint{}
|
||||
|
||||
for statement.Next() {
|
||||
var tp TripPoint
|
||||
err := statement.Scan(
|
||||
&tp.ID,
|
||||
&tp.TripID,
|
||||
&tp.Latitude,
|
||||
&tp.Longitude,
|
||||
&tp.Speed,
|
||||
&tp.RecordedTimeStamp,
|
||||
&tp.Sequence,
|
||||
&tp.RPM,
|
||||
&tp.ShortTermFuelBank,
|
||||
&tp.LongTermFuelBank,
|
||||
&tp.ThrottlePosition,
|
||||
&tp.RelativeThrottlePosition,
|
||||
&tp.Runtime,
|
||||
&tp.DistanceWithMalfunctionLight,
|
||||
&tp.EngineLoad,
|
||||
&tp.EngineFuelRate,
|
||||
&tp.VIN)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Error scanning Trip Points"
|
||||
logError(err, msg)
|
||||
fmt.Fprintf(w, SerializeError(err, msg))
|
||||
return
|
||||
}
|
||||
|
||||
tripPointRows = append(tripPointRows, tp)
|
||||
}
|
||||
|
||||
serializedReturn, _ := json.Marshal(tripPointRows)
|
||||
|
||||
fmt.Fprintf(w, string(serializedReturn))
|
||||
}
|
||||
|
||||
func getTripPointByID(w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
|
||||
tripPointID := params["tripPointID"]
|
||||
|
||||
var query = selectTripPointsForTripPointIDQuery(tripPointID)
|
||||
|
||||
row, err := FirstOrDefault(query)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Error while retrieving trip point from database"
|
||||
logError(err, msg)
|
||||
fmt.Fprintf(w, SerializeError(err, msg))
|
||||
return
|
||||
}
|
||||
|
||||
var tripPoint TripPoint
|
||||
|
||||
err = row.Scan(
|
||||
&tripPoint.ID,
|
||||
&tripPoint.TripID,
|
||||
&tripPoint.Latitude,
|
||||
&tripPoint.Longitude,
|
||||
&tripPoint.Speed,
|
||||
&tripPoint.RecordedTimeStamp,
|
||||
&tripPoint.Sequence,
|
||||
&tripPoint.RPM,
|
||||
&tripPoint.ShortTermFuelBank,
|
||||
&tripPoint.LongTermFuelBank,
|
||||
&tripPoint.ThrottlePosition,
|
||||
&tripPoint.RelativeThrottlePosition,
|
||||
&tripPoint.Runtime,
|
||||
&tripPoint.DistanceWithMalfunctionLight,
|
||||
&tripPoint.EngineLoad,
|
||||
&tripPoint.EngineFuelRate,
|
||||
&tripPoint.VIN)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Failed to scan a trip point"
|
||||
logError(err, msg)
|
||||
fmt.Fprintf(w, SerializeError(err, msg))
|
||||
return
|
||||
}
|
||||
|
||||
serializedTripPoint, _ := json.Marshal(tripPoint)
|
||||
|
||||
fmt.Fprintf(w, string(serializedTripPoint))
|
||||
}
|
||||
|
||||
func createTripPoint(w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
|
||||
tripID := params["tripID"]
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
|
||||
var tripPoint TripPoint
|
||||
|
||||
err = json.Unmarshal(body, &tripPoint)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Error while decoding json for trip point"
|
||||
logError(err, msg)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, SerializeError(err, msg))
|
||||
return
|
||||
}
|
||||
|
||||
var query = createTripPointQuery(tripPoint, tripID)
|
||||
|
||||
result, err := ExecuteQuery(query)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Error while inserting Trip Point into database"
|
||||
logError(err, msg)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, SerializeError(err, msg))
|
||||
return
|
||||
}
|
||||
|
||||
for result.Next() {
|
||||
err = result.Scan(&tripPoint.ID)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Error retrieving trip point id"
|
||||
logError(err, msg)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, SerializeError(err, msg))
|
||||
}
|
||||
}
|
||||
|
||||
serializedTripPoint, _ := json.Marshal(tripPoint)
|
||||
|
||||
fmt.Fprintf(w, string(serializedTripPoint))
|
||||
}
|
||||
|
||||
func updateTripPoint(w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
|
||||
tripPointID := params["tripPointID"]
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
var msg = "Error while decoding json for trip point"
|
||||
logError(err, msg)
|
||||
fmt.Fprintf(w, SerializeError(err, msg))
|
||||
return
|
||||
}
|
||||
|
||||
var tripPoint TripPoint
|
||||
|
||||
err = json.Unmarshal(body, &tripPoint)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Error while decoding json"
|
||||
logError(err, msg)
|
||||
fmt.Fprintf(w, SerializeError(err, msg))
|
||||
return
|
||||
}
|
||||
|
||||
tripPoint.ID = tripPointID
|
||||
|
||||
var query = updateTripPointQuery(tripPoint)
|
||||
|
||||
result, err := ExecuteNonQuery(query)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Error while patching Trip Point on the database"
|
||||
logError(err, msg)
|
||||
fmt.Fprintf(w, SerializeError(err, msg))
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, string(result))
|
||||
}
|
||||
|
||||
func deleteTripPoint(w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
|
||||
tripPointID := params["tripPointID"]
|
||||
|
||||
var query = deleteTripPointQuery(tripPointID)
|
||||
|
||||
result, err := ExecuteNonQuery(query)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Error while deleting trip point from database"
|
||||
logError(err, msg)
|
||||
fmt.Fprintf(w, SerializeError(err, msg))
|
||||
return
|
||||
}
|
||||
|
||||
serializedResult, _ := json.Marshal(result)
|
||||
|
||||
fmt.Fprintf(w, string(serializedResult))
|
||||
}
|
||||
|
||||
// func getMaxSequence(w http.ResponseWriter, r *http.Request) {
|
||||
// tripID := r.FormValue("id")
|
||||
|
||||
// query := fmt.Sprintf("SELECT MAX(Sequence) as MaxSequence FROM TripPoints where tripid = '%s'", tripID)
|
||||
|
||||
// row, err := FirstOrDefault(query)
|
||||
|
||||
// if err != nil {
|
||||
// var msg = "Error while querying Max Sequence"
|
||||
// logError(err, msg)
|
||||
// fmt.Fprintf(w, SerializeError(err, msg))
|
||||
// return
|
||||
// }
|
||||
|
||||
// var MaxSequence string
|
||||
|
||||
// err = row.Scan(&MaxSequence)
|
||||
|
||||
// if err != nil {
|
||||
// var msg = "Error while obtaining max sequence"
|
||||
// logError(err, msg)
|
||||
// fmt.Fprintf(w, SerializeError(err, msg))
|
||||
// return
|
||||
// }
|
||||
|
||||
// fmt.Fprintf(w, MaxSequence)
|
||||
// }
|
||||
|
||||
type newTripPoint struct {
|
||||
ID string
|
||||
}
|
295
apis/trips/tripsgo/tripService.go
Normal file
295
apis/trips/tripsgo/tripService.go
Normal file
@ -0,0 +1,295 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
_ "github.com/denisenkom/go-mssqldb" //vscode deletes this import if it is not a blank import
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// Trip Service Methods
|
||||
|
||||
// getTripByID - gets a trip by its trip id
|
||||
func getTripByID(w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
|
||||
//Build Query
|
||||
var query = SelectTripByIDQuery(params["tripID"])
|
||||
|
||||
//Execute Query
|
||||
row, err := FirstOrDefault(query)
|
||||
|
||||
if err != nil {
|
||||
var msg = "getTripsByID - Error while retrieving trip from database"
|
||||
logError(err, msg)
|
||||
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var trip Trip
|
||||
|
||||
errScan := row.Scan(
|
||||
&trip.ID,
|
||||
&trip.Name,
|
||||
&trip.UserID,
|
||||
&trip.RecordedTimeStamp,
|
||||
&trip.EndTimeStamp,
|
||||
&trip.Rating,
|
||||
&trip.IsComplete,
|
||||
&trip.HasSimulatedOBDData,
|
||||
&trip.AverageSpeed,
|
||||
&trip.FuelUsed,
|
||||
&trip.HardStops,
|
||||
&trip.HardAccelerations,
|
||||
&trip.Distance,
|
||||
&trip.Created,
|
||||
&trip.UpdatedAt)
|
||||
|
||||
if errScan != nil {
|
||||
var msg = fmt.Sprintf("No trip with ID '%s' found", params["tripID"])
|
||||
logMessage(msg)
|
||||
// fmt.Fprintf(w, msg)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
serializedTrip, _ := json.Marshal(trip)
|
||||
|
||||
fmt.Fprintf(w, string(serializedTrip))
|
||||
}
|
||||
|
||||
// getAllTrips - get all trips
|
||||
func getAllTrips(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var query = SelectAllTripsQuery()
|
||||
|
||||
tripRows, err := ExecuteQuery(query)
|
||||
|
||||
if err != nil {
|
||||
var msg = "getAllTrips - Query Failed to Execute."
|
||||
logError(err, msg)
|
||||
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
trips := []Trip{}
|
||||
|
||||
for tripRows.Next() {
|
||||
var r Trip
|
||||
err := tripRows.Scan(
|
||||
&r.ID,
|
||||
&r.Name,
|
||||
&r.UserID,
|
||||
&r.RecordedTimeStamp,
|
||||
&r.EndTimeStamp,
|
||||
&r.Rating,
|
||||
&r.IsComplete,
|
||||
&r.HasSimulatedOBDData,
|
||||
&r.AverageSpeed,
|
||||
&r.FuelUsed,
|
||||
&r.HardStops,
|
||||
&r.HardAccelerations,
|
||||
&r.Distance,
|
||||
&r.Created,
|
||||
&r.UpdatedAt)
|
||||
|
||||
if err != nil {
|
||||
var msg = "GetAllTrips - Error scanning Trips"
|
||||
logError(err, msg)
|
||||
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
trips = append(trips, r)
|
||||
}
|
||||
|
||||
tripsJSON, _ := json.Marshal(trips)
|
||||
|
||||
fmt.Fprintf(w, string(tripsJSON))
|
||||
}
|
||||
|
||||
// getAllTripsForUser - get all trips for a given user
|
||||
func getAllTripsForUser(w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
|
||||
var query = SelectAllTripsForUserQuery(params["userID"])
|
||||
|
||||
tripRows, err := ExecuteQuery(query)
|
||||
|
||||
if err != nil {
|
||||
var msg = "getAllTripsForUser - Error while retrieving trips from database"
|
||||
logError(err, msg)
|
||||
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
trips := []Trip{}
|
||||
|
||||
for tripRows.Next() {
|
||||
var r Trip
|
||||
err := tripRows.Scan(&r.ID,
|
||||
&r.Name,
|
||||
&r.UserID,
|
||||
&r.RecordedTimeStamp,
|
||||
&r.EndTimeStamp,
|
||||
&r.Rating,
|
||||
&r.IsComplete,
|
||||
&r.HasSimulatedOBDData,
|
||||
&r.AverageSpeed,
|
||||
&r.FuelUsed,
|
||||
&r.HardStops,
|
||||
&r.HardAccelerations,
|
||||
&r.Distance,
|
||||
&r.Created,
|
||||
&r.UpdatedAt)
|
||||
|
||||
if err != nil {
|
||||
var msg = "getAllTripsForUser - Error scanning Trips"
|
||||
logError(err, msg)
|
||||
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
trips = append(trips, r)
|
||||
}
|
||||
|
||||
tripsJSON, _ := json.Marshal(trips)
|
||||
|
||||
fmt.Fprintf(w, string(tripsJSON))
|
||||
}
|
||||
|
||||
// deleteTrip - deletes a single trip and its associated trip points for a user
|
||||
func deleteTrip(w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
|
||||
var deleteTripPointsQuery = DeleteTripPointsForTripQuery(params["tripID"])
|
||||
var deleteTripsQuery = DeleteTripQuery(params["tripID"])
|
||||
|
||||
result, err := ExecuteNonQuery(deleteTripPointsQuery)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Error while deleting trip points from database"
|
||||
logError(err, msg)
|
||||
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Debug.Println(fmt.Sprintln(`Deleted trip points for Trip '%s'`, params["tripID"]))
|
||||
|
||||
result, err = ExecuteNonQuery(deleteTripsQuery)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Error while deleting trip from database"
|
||||
logError(err, msg)
|
||||
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Debug.Println(fmt.Sprintln("Deleted trip '%s'", params["tripID"]))
|
||||
|
||||
serializedResult, _ := json.Marshal(result)
|
||||
|
||||
fmt.Fprintf(w, string(serializedResult))
|
||||
}
|
||||
|
||||
// updateTrip - update a trip
|
||||
func updateTrip(w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
|
||||
tripID := params["tripID"]
|
||||
|
||||
var trip Trip
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
var msg = "Update Trip - Error reading trip request body"
|
||||
logError(err, msg)
|
||||
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &trip)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Update Trip - Error while decoding trip json"
|
||||
logError(err, msg)
|
||||
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
trip.ID = tripID
|
||||
|
||||
updateQuery := UpdateTripQuery(trip)
|
||||
|
||||
result, err := ExecuteNonQuery(updateQuery)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Error updating trip on the database." + string(result)
|
||||
logError(err, msg)
|
||||
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
serializedTrip, _ := json.Marshal(trip)
|
||||
|
||||
fmt.Fprintf(w, string(serializedTrip))
|
||||
}
|
||||
|
||||
// createTrip - create a trip for a user. This method does not create the associated trip points, only the trip.
|
||||
func createTrip(w http.ResponseWriter, r *http.Request) {
|
||||
//params := mux.Vars(r)
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
|
||||
var trip Trip
|
||||
|
||||
err = json.Unmarshal(body, &trip)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Error while decoding json"
|
||||
logError(err, msg)
|
||||
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
insertQuery := createTripQuery(trip)
|
||||
|
||||
var newTripID newTrip
|
||||
|
||||
result, err := ExecuteQuery(insertQuery)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Error while inserting trip into database"
|
||||
logError(err, msg)
|
||||
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
for result.Next() {
|
||||
err = result.Scan(&newTripID.ID)
|
||||
|
||||
if err != nil {
|
||||
var msg = "Error while retrieving last id"
|
||||
logError(err, msg)
|
||||
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
trip.ID = newTripID.ID
|
||||
|
||||
serializedTrip, _ := json.Marshal(trip)
|
||||
|
||||
fmt.Fprintf(w, string(serializedTrip))
|
||||
}
|
||||
|
||||
type newTrip struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// End of Trip Service Methods
|
436
apis/trips/tripsgo/tripService_test.go
Normal file
436
apis/trips/tripsgo/tripService_test.go
Normal file
@ -0,0 +1,436 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var tripID string
|
||||
|
||||
var apiTestList = []APITestCase{
|
||||
{
|
||||
Tag: "t1 - Get all trips",
|
||||
Method: "GET",
|
||||
URL: "/api/trips",
|
||||
Status: 200,
|
||||
},
|
||||
{
|
||||
Tag: "t2 - Get a nonexistent trip",
|
||||
Method: "GET",
|
||||
URL: "/api/trips/99999",
|
||||
Status: 404,
|
||||
},
|
||||
{
|
||||
Tag: "t3 - Create a Trip",
|
||||
Method: "POST",
|
||||
URL: "/api/trips",
|
||||
Body: `{
|
||||
"Name":"Trip CREATE TEST",
|
||||
"UserId":"GO_TEST",
|
||||
"RecordedTimeStamp": "2018-04-19T19:08:16.03Z",
|
||||
"EndTimeStamp": "2018-04-19T19:42:49.573Z",
|
||||
"Rating":95,
|
||||
"IsComplete":false,
|
||||
"HasSimulatedOBDData":true,
|
||||
"AverageSpeed":100,
|
||||
"FuelUsed":10.27193484,
|
||||
"HardStops":2,
|
||||
"HardAccelerations":4,
|
||||
"Distance":30.0275486,
|
||||
"CreatedAt":"2018-01-01T12:00:00Z",
|
||||
"UpdatedAt":"2001-01-01T12:00:00Z"
|
||||
}`,
|
||||
Status: 200,
|
||||
},
|
||||
{
|
||||
Tag: "t4 - Update a trip",
|
||||
Method: "PATCH",
|
||||
URL: "/api/trips/{tripID}",
|
||||
Body: `{
|
||||
"Name":"Trip UPDATE TEST",
|
||||
"UserId":"GO_TEST",
|
||||
"RecordedTimeStamp": "2018-04-19T19:08:16.03Z",
|
||||
"EndTimeStamp": "2018-04-19T19:42:49.573Z",
|
||||
"Rating":91005,
|
||||
"IsComplete":true,
|
||||
"HasSimulatedOBDData":true,
|
||||
"AverageSpeed":100,
|
||||
"FuelUsed":10.27193484,
|
||||
"HardStops":2,
|
||||
"HardAccelerations":4,
|
||||
"Distance":30.0275486,
|
||||
"CreatedAt":"2018-01-01T12:00:00Z",
|
||||
"UpdatedAt":"2001-01-01T12:00:00Z"
|
||||
}`,
|
||||
Status: 200,
|
||||
},
|
||||
{
|
||||
Tag: "t5 - Create Trip Point",
|
||||
Method: "POST",
|
||||
URL: "/api/trips/{tripID}/trippoints",
|
||||
Body: `{
|
||||
"TripId": "{tripID}",
|
||||
"Latitude": 47.67598,
|
||||
"Longitude": -122.10612,
|
||||
"Speed": -255,
|
||||
"RecordedTimeStamp": "2018-05-24T10:00:15.003Z",
|
||||
"Sequence": 2,
|
||||
"RPM": -255,
|
||||
"ShortTermFuelBank": -255,
|
||||
"LongTermFuelBank": -255,
|
||||
"ThrottlePosition": -255,
|
||||
"RelativeThrottlePosition": -255,
|
||||
"Runtime": -255,
|
||||
"DistanceWithMalfunctionLight": -255,
|
||||
"EngineLoad": -255,
|
||||
"EngineFuelRate": -255,
|
||||
"CreatedAt": "0001-01-01T00:00:00Z",
|
||||
"UpdatedAt": "0001-01-01T00:00:00Z"
|
||||
}`,
|
||||
Status: 200,
|
||||
},
|
||||
{
|
||||
Tag: "t6 - Update Trip Point",
|
||||
Method: "PATCH",
|
||||
URL: "/api/trips/{tripID}/trippoints/{tripPointID}",
|
||||
Body: `{
|
||||
"Id": "{tripPointID}",
|
||||
"TripId": "{tripID}",
|
||||
"Latitude": 47.67598,
|
||||
"Longitude": -122.10612,
|
||||
"Speed": -255,
|
||||
"RecordedTimeStamp": "2018-05-24T10:00:15.003Z",
|
||||
"Sequence": 2,
|
||||
"RPM": -255,
|
||||
"ShortTermFuelBank": -255,
|
||||
"LongTermFuelBank": -255,
|
||||
"ThrottlePosition": -255,
|
||||
"RelativeThrottlePosition": -255,
|
||||
"Runtime": -255,
|
||||
"DistanceWithMalfunctionLight": -255,
|
||||
"EngineLoad": -255,
|
||||
"EngineFuelRate": -255,
|
||||
"Created": "0001-01-01T00:00:00Z",
|
||||
"UpdatedAt": "0001-01-01T00:00:00Z"
|
||||
}`,
|
||||
ExpectedResponse: "",
|
||||
Status: 200,
|
||||
},
|
||||
{
|
||||
Tag: "t7 - Read Trip Points for Trip",
|
||||
Method: "GET",
|
||||
URL: "/api/trips/{tripID}/trippoints",
|
||||
Status: 200,
|
||||
},
|
||||
{
|
||||
Tag: "t8 - Read Trip Points By Trip Point ID",
|
||||
Method: "GET",
|
||||
URL: "/api/trips/{tripID}/trippoints/{tripPointID}",
|
||||
Status: 200,
|
||||
},
|
||||
{
|
||||
Tag: "t9 - Delete Trip Point",
|
||||
Method: "DELETE",
|
||||
URL: "/api/trips/{tripID}/trippoints/{tripPointID}",
|
||||
Status: 200,
|
||||
},
|
||||
{
|
||||
Tag: "t10 - Delete a Trip",
|
||||
Method: "DELETE",
|
||||
URL: "/api/trips/{tripID}",
|
||||
Status: 200,
|
||||
},
|
||||
{
|
||||
Tag: "t11 - Get All Trips for User",
|
||||
Method: "GET",
|
||||
URL: "/api/trips/user/SomeUser",
|
||||
Status: 200,
|
||||
},
|
||||
}
|
||||
|
||||
func TestTripApis(t *testing.T) {
|
||||
router := NewRouter()
|
||||
var debug, present = os.LookupEnv("DEBUG_LOGGING")
|
||||
|
||||
if present && debug == "true" {
|
||||
InitLogging(os.Stdout, os.Stdout, os.Stdout)
|
||||
} else {
|
||||
// if debug env is not present or false, do not log debug output to console
|
||||
InitLogging(os.Stdout, ioutil.Discard, os.Stdout)
|
||||
}
|
||||
RunAPITests(t, router, apiTestList[0:3])
|
||||
|
||||
// setup update trip test (URL, Body, expected Response)
|
||||
apiTestList[3].URL = strings.Replace(apiTestList[3].URL, "{tripID}", TripFromStr(apiTestList[2].ActualResponse).ID, 1)
|
||||
apiTestList[3].Body = GetUpdateTrip(apiTestList[2].ActualResponse, apiTestList[3].Body)
|
||||
apiTestList[3].ExpectedResponse = apiTestList[3].Body
|
||||
|
||||
// setup create trip point test
|
||||
apiTestList[4].URL = strings.Replace(apiTestList[4].URL, "{tripID}", TripFromStr(apiTestList[2].ActualResponse).ID, 1)
|
||||
apiTestList[4].Body = strings.Replace(apiTestList[4].Body, "{tripID}", TripFromStr(apiTestList[2].ActualResponse).ID, 1)
|
||||
|
||||
// run update trip and create trip point tests
|
||||
RunAPITests(t, router, apiTestList[3:5])
|
||||
|
||||
// setup update trip point test
|
||||
apiTestList[5].URL = strings.Replace(apiTestList[5].URL, "{tripID}", TripFromStr(apiTestList[2].ActualResponse).ID, 1)
|
||||
apiTestList[5].URL = strings.Replace(apiTestList[5].URL, "{tripPointID}", TripPointFromStr(apiTestList[4].ActualResponse).ID, 1)
|
||||
apiTestList[5].Body = GetUpdateTripPoint(apiTestList[4].ActualResponse, apiTestList[5].Body)
|
||||
|
||||
// setup read trip points for trip test
|
||||
apiTestList[6].URL = strings.Replace(apiTestList[6].URL, "{tripID}", TripFromStr(apiTestList[2].ActualResponse).ID, 1)
|
||||
|
||||
// // setup ready trip points by trip point id test
|
||||
apiTestList[7].URL = strings.Replace(apiTestList[7].URL, "{tripID}", TripFromStr(apiTestList[2].ActualResponse).ID, 1)
|
||||
apiTestList[7].URL = strings.Replace(apiTestList[7].URL, "{tripPointID}", TripPointFromStr(apiTestList[4].ActualResponse).ID, 1)
|
||||
|
||||
// //setup delete trip point test
|
||||
apiTestList[8].URL = strings.Replace(apiTestList[8].URL, "{tripID}", TripFromStr(apiTestList[2].ActualResponse).ID, 1)
|
||||
apiTestList[8].URL = strings.Replace(apiTestList[8].URL, "{tripPointID}", TripPointFromStr(apiTestList[4].ActualResponse).ID, 1)
|
||||
|
||||
// setup delete test (URL)
|
||||
apiTestList[9].URL = strings.Replace(apiTestList[9].URL, "{tripID}", TripFromStr(apiTestList[2].ActualResponse).ID, 1)
|
||||
// run update test
|
||||
RunAPITests(t, router, apiTestList[5:10])
|
||||
|
||||
RunAPITests(t, router, apiTestList[10:11])
|
||||
}
|
||||
|
||||
func TestGetAllTripsReturnsServerErrorIfBadDbConnection(t *testing.T) {
|
||||
defer t.Cleanup(resetDataAccessEnvVars)
|
||||
|
||||
//arrange
|
||||
os.Setenv("SQL_DRIVER", "not_a_real_driver")
|
||||
RebindDataAccessEnvironmentVariables()
|
||||
info := new(bytes.Buffer)
|
||||
InitLogging(info, os.Stdout, os.Stdout)
|
||||
var tr bool = true
|
||||
debug = &tr
|
||||
|
||||
//act
|
||||
router := NewRouter()
|
||||
RunAPITestsPlainText(t, router, []APITestCase{
|
||||
{
|
||||
Tag: "t1 - Get all trips",
|
||||
Method: "GET",
|
||||
URL: "/api/trips",
|
||||
Status: 500,
|
||||
},
|
||||
})
|
||||
|
||||
//assert
|
||||
actual := fmt.Sprint(info)
|
||||
assert.Contains(t, actual, "getAllTrips - Query Failed to Execute")
|
||||
}
|
||||
|
||||
func TestGetAllTripsReturnsErrorScanningTripsIfMissingSqlFields(t *testing.T) {
|
||||
defer t.Cleanup(resetDataAccessEnvVars)
|
||||
|
||||
//arrange
|
||||
//oldSelectAllTripsQuery := func(SelectAllTripsQuery)
|
||||
var OldSelectAllTripsQuery = SelectAllTripsQuery
|
||||
SelectAllTripsQuery = func() string {
|
||||
return `SELECT
|
||||
Id
|
||||
FROM Trips
|
||||
WHERE Deleted = 0`
|
||||
}
|
||||
defer func() { SelectAllTripsQuery = OldSelectAllTripsQuery }()
|
||||
|
||||
info := new(bytes.Buffer)
|
||||
InitLogging(info, os.Stdout, os.Stdout)
|
||||
var tr bool = true
|
||||
debug = &tr
|
||||
|
||||
//act
|
||||
router := NewRouter()
|
||||
RunAPITestsPlainText(t, router, []APITestCase{
|
||||
{
|
||||
Tag: "t1 - Get all trips",
|
||||
Method: "GET",
|
||||
URL: "/api/trips",
|
||||
Status: 500,
|
||||
},
|
||||
})
|
||||
|
||||
//assert
|
||||
actual := fmt.Sprint(info)
|
||||
assert.Contains(t, actual, "GetAllTrips - Error scanning Trips")
|
||||
}
|
||||
|
||||
func TestGetAllTripsForUsersReturnsServerErrorIfBadDbConnection(t *testing.T) {
|
||||
defer t.Cleanup(resetDataAccessEnvVars)
|
||||
|
||||
//arrange
|
||||
os.Setenv("SQL_DRIVER", "not_a_real_driver")
|
||||
RebindDataAccessEnvironmentVariables()
|
||||
info := new(bytes.Buffer)
|
||||
InitLogging(info, os.Stdout, os.Stdout)
|
||||
var tr bool = true
|
||||
debug = &tr
|
||||
|
||||
//act
|
||||
router := NewRouter()
|
||||
RunAPITestsPlainText(t, router, []APITestCase{
|
||||
{
|
||||
Tag: "t11 - Get All Trips for User",
|
||||
Method: "GET",
|
||||
URL: "/api/trips/user/SomeUser",
|
||||
Status: 500,
|
||||
},
|
||||
})
|
||||
|
||||
//assert
|
||||
actual := fmt.Sprint(info)
|
||||
assert.Contains(t, actual, "getAllTripsForUser - Error while retrieving trips from database")
|
||||
}
|
||||
|
||||
func TestGetAllTripsForUserReturnsErrorScanningTripsIfMissingSqlFields(t *testing.T) {
|
||||
defer t.Cleanup(resetDataAccessEnvVars)
|
||||
|
||||
//arrange
|
||||
//oldSelectAllTripsQuery := func(SelectAllTripsQuery)
|
||||
var OldSelectAllTripsForUserQuery = SelectAllTripsForUserQuery
|
||||
SelectAllTripsForUserQuery = func(userID string) string {
|
||||
return `SELECT
|
||||
Id
|
||||
FROM Trips
|
||||
WHERE UserId ='` + userID + `'
|
||||
AND Deleted = 0`
|
||||
}
|
||||
defer func() { SelectAllTripsForUserQuery = OldSelectAllTripsForUserQuery }()
|
||||
|
||||
info := new(bytes.Buffer)
|
||||
InitLogging(info, os.Stdout, os.Stdout)
|
||||
var tr bool = true
|
||||
debug = &tr
|
||||
|
||||
//act
|
||||
router := NewRouter()
|
||||
RunAPITestsPlainText(t, router, []APITestCase{
|
||||
{
|
||||
Tag: "t11 - Get All Trips for User",
|
||||
Method: "GET",
|
||||
URL: "/api/trips/user/SomeUser",
|
||||
Status: 500,
|
||||
},
|
||||
})
|
||||
|
||||
//assert
|
||||
actual := fmt.Sprint(info)
|
||||
assert.Contains(t, actual, "getAllTripsForUser - Error scanning Trips")
|
||||
}
|
||||
|
||||
func TestCreateTripReturnsErrorifInvalidJsonBody(t *testing.T) {
|
||||
info := new(bytes.Buffer)
|
||||
InitLogging(info, os.Stdout, os.Stdout)
|
||||
|
||||
//act
|
||||
router := NewRouter()
|
||||
RunAPITestsPlainText(t, router, []APITestCase{
|
||||
{
|
||||
Tag: "t3 - Create a Trip",
|
||||
Method: "POST",
|
||||
URL: "/api/trips",
|
||||
Body: `{
|
||||
"Name":"Trip CREATE TEST",
|
||||
"UserId":"GO_TEST",
|
||||
"RecordedTimeStamp": "2018-04-19T19:08:16.03Z",
|
||||
"EndTimeStamp": "2018-04-19T19:42:49.573Z",
|
||||
"Rating":95,
|
||||
"IsComplete":`,
|
||||
Status: 500,
|
||||
},
|
||||
})
|
||||
|
||||
//assert
|
||||
actual := fmt.Sprint(info)
|
||||
assert.Contains(t, actual, "Error while decoding json")
|
||||
}
|
||||
|
||||
func TestUpdateTripReturnsErrorifInvalidJsonBody(t *testing.T) {
|
||||
info := new(bytes.Buffer)
|
||||
InitLogging(info, os.Stdout, os.Stdout)
|
||||
|
||||
//act
|
||||
router := NewRouter()
|
||||
RunAPITestsPlainText(t, router, []APITestCase{
|
||||
{
|
||||
Tag: "t4 - Update a trip",
|
||||
Method: "PATCH",
|
||||
URL: "/api/trips/{tripID}",
|
||||
Body: `{
|
||||
"Name":"Trip UPDATE TEST",
|
||||
"UserId":"GO_TEST",
|
||||
"RecordedTimeStamp": "2018-04-19T19:08:16.03Z",
|
||||
"EndTimeStamp": "2018-04-19T19:42:49.573Z",
|
||||
"Rating":`,
|
||||
Status: 500,
|
||||
},
|
||||
})
|
||||
|
||||
//assert
|
||||
actual := fmt.Sprint(info)
|
||||
assert.Contains(t, actual, "Update Trip - Error while decoding trip json")
|
||||
}
|
||||
|
||||
func GetUpdateTrip(tripCreate string, tripUpdate string) string {
|
||||
tripC := TripFromStr(tripCreate)
|
||||
tripU := TripFromStr(tripUpdate)
|
||||
|
||||
tripU.ID = tripC.ID
|
||||
|
||||
serializedTripUpdate, _ := json.Marshal(tripU)
|
||||
|
||||
return string(serializedTripUpdate)
|
||||
}
|
||||
|
||||
func GetUpdateTripPoint(tripPointCreate string, tripPointUpdate string) string {
|
||||
tripPointC := TripPointFromStr(tripPointCreate)
|
||||
tripPointU := TripPointFromStr(tripPointUpdate)
|
||||
|
||||
tripPointU.ID = tripPointC.ID
|
||||
tripPointU.TripID = tripPointC.TripID
|
||||
|
||||
serializedTripUpdate, _ := json.Marshal(tripPointU)
|
||||
|
||||
return string(serializedTripUpdate)
|
||||
}
|
||||
|
||||
func TripFromStr(tripStr string) Trip {
|
||||
trip := Trip{}
|
||||
|
||||
Debug.Println("DEBUG: TripFromStr - " + tripStr)
|
||||
|
||||
errCreate := json.Unmarshal([]byte(tripStr), &trip)
|
||||
if errCreate != nil {
|
||||
log.Println("TripFromStr - Invalid trip string")
|
||||
log.Fatal(errCreate)
|
||||
}
|
||||
|
||||
return trip
|
||||
}
|
||||
|
||||
func TripPointFromStr(tripPointStr string) TripPoint {
|
||||
tripPoint := TripPoint{}
|
||||
|
||||
Debug.Println("DEBUG: TripPointFromStr - " + tripPointStr)
|
||||
|
||||
errCreate := json.Unmarshal([]byte(tripPointStr), &tripPoint)
|
||||
if errCreate != nil {
|
||||
log.Println("TripPointFromStr - Invalid trip point string")
|
||||
log.Fatal(errCreate)
|
||||
}
|
||||
|
||||
Debug.Println(tripPointStr)
|
||||
|
||||
return tripPoint
|
||||
}
|
58
apis/trips/tripsgo/trip_point.go
Normal file
58
apis/trips/tripsgo/trip_point.go
Normal file
@ -0,0 +1,58 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TripPoint - Represents a single point record in a trip
|
||||
type TripPoint struct {
|
||||
|
||||
// Trip Point ID
|
||||
ID string `json:"Id,omitempty"`
|
||||
|
||||
// Trip ID
|
||||
TripID string `json:"TripId,omitempty"`
|
||||
|
||||
Latitude float32 `json:"Latitude,omitempty"`
|
||||
|
||||
Longitude float32 `json:"Longitude,omitempty"`
|
||||
|
||||
Speed float32 `json:"Speed,omitempty"`
|
||||
|
||||
RecordedTimeStamp string `json:"RecordedTimeStamp,omitempty"`
|
||||
|
||||
Sequence int32 `json:"Sequence,omitempty"`
|
||||
|
||||
RPM float32 `json:"RPM,omitempty"`
|
||||
|
||||
ShortTermFuelBank float32 `json:"ShortTermFuelBank,omitempty"`
|
||||
|
||||
LongTermFuelBank float32 `json:"LongTermFuelBank,omitempty"`
|
||||
|
||||
ThrottlePosition float32 `json:"ThrottlePosition,omitempty"`
|
||||
|
||||
RelativeThrottlePosition float32 `json:"RelativeThrottlePosition,omitempty"`
|
||||
|
||||
Runtime float32 `json:"Runtime,omitempty"`
|
||||
|
||||
DistanceWithMalfunctionLight float32 `json:"DistanceWithMalfunctionLight,omitempty"`
|
||||
|
||||
EngineLoad float32 `json:"EngineLoad,omitempty"`
|
||||
|
||||
MassFlowRate float32 `json:"MassFlowRate,omitempty"`
|
||||
|
||||
EngineFuelRate float32 `json:"EngineFuelRate,omitempty"`
|
||||
|
||||
VIN sql.NullString `json:"VIN,omitempty"`
|
||||
|
||||
HasOBDData bool `json:"HasOBDData,omitempty"`
|
||||
|
||||
HasSimulatedOBDData bool `json:"HasSimulatedOBDData,omitempty"`
|
||||
|
||||
CreatedAt time.Time `json:"CreatedAt,omitempty"`
|
||||
|
||||
UpdatedAt time.Time `json:"UpdatedAt,omitempty"`
|
||||
|
||||
Deleted bool `json:"Deleted,omitempty"`
|
||||
}
|
15
apis/trips/tripsgo/versionService.go
Normal file
15
apis/trips/tripsgo/versionService.go
Normal file
@ -0,0 +1,15 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
func versionGet(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
version := os.Getenv("APP_VERSION")
|
||||
fmt.Fprintf(w, version)
|
||||
}
|
32
apis/trips/tripsgo/versionService_test.go
Normal file
32
apis/trips/tripsgo/versionService_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
package tripsgo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var versionRouteTests = []APITestCase{
|
||||
{
|
||||
Tag: "versionService",
|
||||
Method: "GET",
|
||||
URL: "/api/version/trips",
|
||||
Status: 200,
|
||||
ExpectedResponse: `test123`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestVersionServiceUnit(t *testing.T) {
|
||||
router := NewRouter()
|
||||
os.Setenv("APP_VERSION", "test123")
|
||||
var debug, present = os.LookupEnv("DEBUG_LOGGING")
|
||||
|
||||
if present && debug == "true" {
|
||||
InitLogging(os.Stdout, os.Stdout, os.Stdout)
|
||||
} else {
|
||||
// if debug env is not present or false, do not log debug output to console
|
||||
InitLogging(os.Stdout, ioutil.Discard, os.Stdout)
|
||||
}
|
||||
RunAPITestsPlainText(t, router, versionRouteTests[0:1])
|
||||
|
||||
}
|
4
apis/user-java/.gitignore
vendored
Normal file
4
apis/user-java/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
target
|
||||
.project
|
||||
.settings
|
||||
.classpath
|
27
apis/user-java/Dockerfile
Normal file
27
apis/user-java/Dockerfile
Normal file
@ -0,0 +1,27 @@
|
||||
# First stage to build the application
|
||||
FROM maven:3.6.3-openjdk-11-slim AS build-env
|
||||
ADD ./pom.xml pom.xml
|
||||
ADD ./src src/
|
||||
RUN mvn clean package
|
||||
|
||||
# build runtime image
|
||||
FROM openjdk:11-jre-slim
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
# docker build argument
|
||||
# This can be specified during the docker build step by adding " --build-arg build_version=<value>"
|
||||
# App version can be accessed via the uri path /api/version/user-java
|
||||
# https://vsupalov.com/docker-build-pass-environment-variables/
|
||||
ARG build_version="user-java default"
|
||||
|
||||
ENV SQL_USER="YourUserName" \
|
||||
SQL_PASSWORD="changeme" \
|
||||
SQL_SERVER="changeme.database.windows.net" \
|
||||
SQL_DBNAME="mydrivingDB" \
|
||||
APP_VERSION=$build_version
|
||||
|
||||
# Add the application's jar to the container
|
||||
COPY --from=build-env target/swagger-spring-1.0.0.jar user-java.jar
|
||||
|
||||
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/user-java.jar"]
|
73
apis/user-java/README.md
Normal file
73
apis/user-java/README.md
Normal file
@ -0,0 +1,73 @@
|
||||
# Overview
|
||||
|
||||
This server was generated by the [swagger-codegen](https://github.com/swagger-api/swagger-codegen) project.
|
||||
By using the [OpenAPI-Spec](https://github.com/swagger-api/swagger-core), you can easily generate a server stub.
|
||||
This is an example of building a swagger-enabled server in Java using the SpringBoot framework.
|
||||
|
||||
The underlying library integrating swagger to SpringBoot is [springfox](https://github.com/springfox/springfox)
|
||||
|
||||
Start your server as an simple java application.
|
||||
|
||||
You can view the api documentation in swagger-ui by pointing to
|
||||
[http://localhost:8080/api/swagger-ui.html](http://localhost:8080/api/swagger-ui.html)
|
||||
|
||||
Change default port value in `src/main/resources/application.properties`
|
||||
|
||||
## Build the project
|
||||
|
||||
1. Install Maven [https://maven.apache.org/install.html](https://maven.apache.org/install.html) and setup the environment path accordingly
|
||||
|
||||
2. Go into the project root directory that has the pom.xml and run `mvn package`
|
||||
|
||||
## Run only the unit tests
|
||||
|
||||
`mvn test`
|
||||
|
||||
## Run the application
|
||||
|
||||
1. Modify the `src/main/resources/application.properties` values with valid SQL Server values.
|
||||
|
||||
```java
|
||||
spring.datasource.url=someurl
|
||||
spring.datasource.username=username
|
||||
spring.datasource.password=password
|
||||
```
|
||||
|
||||
1. Start the API: `mvn spring-boot:run`
|
||||
|
||||
### POST API Example
|
||||
|
||||
This curl command creates a new user with ID 1234.
|
||||
|
||||
```bash
|
||||
curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{ \
|
||||
"createdAt":"2018-08-07", \
|
||||
"deleted": false, \
|
||||
"firstName": "Hacker", \
|
||||
"fuelConsumption": 0, \
|
||||
"hardAccelerations": 0, \
|
||||
"hardStops": 0, \
|
||||
"id": "1234", \
|
||||
"lastName": "Test", \
|
||||
"maxSpeed": 0, \
|
||||
"profilePictureUri": "https://pbs.twimg.com/profile_images/1003946090146693122/IdMjh-FQ_bigger.jpg", \
|
||||
"ranking": 0, \
|
||||
"rating": 0, \
|
||||
"totalDistance": 0, \
|
||||
"totalTime": 0, \
|
||||
"totalTrips": 0, \
|
||||
"updatedAt": "2018-08-07", \
|
||||
"userId": "Hacker3" \
|
||||
}' 'http://localhost:8080/api/user/1234'
|
||||
```
|
||||
|
||||
### PATCH API Example
|
||||
|
||||
This updates the `fuelConsumption` and `hardStops` fields from the user created above.
|
||||
|
||||
```bash
|
||||
curl -X PATCH --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{ \
|
||||
"fuelConsumption":20, \
|
||||
"hardStops":74371 \
|
||||
}
|
||||
```
|
15
apis/user-java/buildtest.sh
Normal file
15
apis/user-java/buildtest.sh
Normal file
@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
# https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
|
||||
|
||||
# clean the output of the previous build
|
||||
mvn clean
|
||||
|
||||
# run unit tests
|
||||
mvn test
|
||||
|
||||
# create distributable package
|
||||
mvn package
|
||||
|
||||
# run integration tests
|
||||
mvn verify
|
115
apis/user-java/pom.xml
Normal file
115
apis/user-java/pom.xml
Normal file
@ -0,0 +1,115 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-spring</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>swagger-spring</name>
|
||||
<version>1.0.0</version>
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<springfox-version>2.9.2</springfox-version>
|
||||
</properties>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>1.5.9.RELEASE</version>
|
||||
</parent>
|
||||
<build>
|
||||
<sourceDirectory>src/main/java</sourceDirectory>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<includes>
|
||||
<include>application.properties</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</dependency>
|
||||
<!--SpringFox dependencies -->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>${springfox-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
<version>${springfox-version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.joschi.jackson</groupId>
|
||||
<artifactId>jackson-datatype-threetenbp</artifactId>
|
||||
<version>2.6.4</version>
|
||||
</dependency>
|
||||
<!-- Bean Validation API support -->
|
||||
<dependency>
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JPA Data (We are going to use Repositories, Entities, Hibernate, etc...) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<version>8.2.2.jre11</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jayway.jsonpath</groupId>
|
||||
<artifactId>json-path</artifactId>
|
||||
<version>2.2.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,22 @@
|
||||
package io.swagger;
|
||||
|
||||
import com.fasterxml.jackson.databind.util.ISO8601DateFormat;
|
||||
import com.fasterxml.jackson.databind.util.ISO8601Utils;
|
||||
|
||||
import java.text.FieldPosition;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
public class RFC3339DateFormat extends ISO8601DateFormat {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// Same as ISO8601DateFormat but serializing milliseconds.
|
||||
@Override
|
||||
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
|
||||
String value = ISO8601Utils.format(date, true);
|
||||
toAppendTo.append(value);
|
||||
return toAppendTo;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package io.swagger;
|
||||
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.ExitCodeGenerator;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
@EntityScan(
|
||||
basePackageClasses = {Swagger2SpringBoot.class, Jsr310JpaConverters.class}
|
||||
)
|
||||
@SpringBootApplication
|
||||
@EnableSwagger2
|
||||
@ComponentScan(basePackages = { "io.swagger", "io.swagger.api" })
|
||||
public class Swagger2SpringBoot implements CommandLineRunner {
|
||||
|
||||
@Override
|
||||
public void run(String... arg0) throws Exception {
|
||||
if (arg0.length > 0 && arg0[0].equals("exitcode")) {
|
||||
throw new ExitException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
new SpringApplication(Swagger2SpringBoot.class).run(args);
|
||||
}
|
||||
|
||||
class ExitException extends RuntimeException implements ExitCodeGenerator {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package io.swagger.api;
|
||||
|
||||
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-08-03T19:26:46.543Z")
|
||||
|
||||
public class ApiException extends Exception{
|
||||
private int code;
|
||||
public ApiException (int code, String msg) {
|
||||
super(msg);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package io.swagger.api;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-08-03T19:26:46.543Z")
|
||||
|
||||
public class ApiOriginFilter implements javax.servlet.Filter {
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response,
|
||||
FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletResponse res = (HttpServletResponse) response;
|
||||
res.addHeader("Access-Control-Allow-Origin", "*");
|
||||
res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
|
||||
res.addHeader("Access-Control-Allow-Headers", "Content-Type");
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package io.swagger.api;
|
||||
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
|
||||
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-08-03T19:26:46.543Z")
|
||||
|
||||
@javax.xml.bind.annotation.XmlRootElement
|
||||
public class ApiResponseMessage {
|
||||
public static final int ERROR = 1;
|
||||
public static final int WARNING = 2;
|
||||
public static final int INFO = 3;
|
||||
public static final int OK = 4;
|
||||
public static final int TOO_BUSY = 5;
|
||||
|
||||
int code;
|
||||
String type;
|
||||
String message;
|
||||
|
||||
public ApiResponseMessage(){}
|
||||
|
||||
public ApiResponseMessage(int code, String message){
|
||||
this.code = code;
|
||||
switch(code){
|
||||
case ERROR:
|
||||
setType("error");
|
||||
break;
|
||||
case WARNING:
|
||||
setType("warning");
|
||||
break;
|
||||
case INFO:
|
||||
setType("info");
|
||||
break;
|
||||
case OK:
|
||||
setType("ok");
|
||||
break;
|
||||
case TOO_BUSY:
|
||||
setType("too busy");
|
||||
break;
|
||||
default:
|
||||
setType("unknown");
|
||||
break;
|
||||
}
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@XmlTransient
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* NOTE: This class is auto generated by the swagger code generator program (2.3.1).
|
||||
* https://github.com/swagger-api/swagger-codegen
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
package io.swagger.api;
|
||||
|
||||
import io.swagger.model.ErrorResponseDefault;
|
||||
import io.swagger.model.Healthcheck;
|
||||
import io.swagger.annotations.*;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-08-03T19:26:46.543Z")
|
||||
|
||||
@Api(value = "User Java", description = "User-Java API", tags = { "User Java" })
|
||||
public interface HealthcheckApi {
|
||||
|
||||
@ApiOperation(value = "", nickname = "healthcheckUserGet", notes = "Returns healthcheck for systems looking to ensure API is up and operational", response = Healthcheck.class, tags={ })
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(code = 200, message = "Service is healthy", response = Healthcheck.class),
|
||||
@ApiResponse(code = 200, message = "An error occurred", response = ErrorResponseDefault.class) })
|
||||
@RequestMapping(value = "/healthcheck/user-java",
|
||||
produces = { "application/json" },
|
||||
method = RequestMethod.GET)
|
||||
ResponseEntity<Healthcheck> healthcheckUserGet();
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package io.swagger.api;
|
||||
|
||||
import io.swagger.model.Healthcheck;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-08-03T19:26:46.543Z")
|
||||
|
||||
@Controller
|
||||
public class HealthcheckApiController implements HealthcheckApi {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(HealthcheckApiController.class);
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final HttpServletRequest request;
|
||||
|
||||
@org.springframework.beans.factory.annotation.Autowired
|
||||
public HealthcheckApiController(ObjectMapper objectMapper, HttpServletRequest request) {
|
||||
this.objectMapper = objectMapper;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public ResponseEntity<Healthcheck> healthcheckUserGet() {
|
||||
|
||||
try {
|
||||
return new ResponseEntity<Healthcheck>(objectMapper.readValue("{ \"message\" : \"User-Java Service Healthcheck\", \"status\" : \"healthy\"}", Healthcheck.class), HttpStatus.OK);
|
||||
} catch (IOException e) {
|
||||
log.error("Couldn't serialize response for content type application/json", e);
|
||||
return new ResponseEntity<Healthcheck>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package io.swagger.api;
|
||||
|
||||
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-08-03T19:26:46.543Z")
|
||||
|
||||
public class NotFoundException extends ApiException {
|
||||
private int code;
|
||||
public NotFoundException (int code, String msg) {
|
||||
super(code, msg);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
43
apis/user-java/src/main/java/io/swagger/api/UserApi.java
Normal file
43
apis/user-java/src/main/java/io/swagger/api/UserApi.java
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* NOTE: This class is auto generated by the swagger code generator program (2.3.1).
|
||||
* https://github.com/swagger-api/swagger-codegen
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
package io.swagger.api;
|
||||
|
||||
import io.swagger.model.ErrorResponseDefault;
|
||||
import io.swagger.model.InlineResponseDefault;
|
||||
import io.swagger.model.Profile;
|
||||
import io.swagger.annotations.*;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
import javax.validation.Valid;
|
||||
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-08-03T19:26:46.543Z")
|
||||
|
||||
@Api(value = "User Java", description = "User-Java API", tags = { "User Java" })
|
||||
public interface UserApi {
|
||||
|
||||
@ApiOperation(value = "", nickname = "updateUser", notes = "Update User", response = Profile.class, tags={ })
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(code = 200, message = "User Updated", response = Profile.class),
|
||||
@ApiResponse(code = 404, message = "User profile not found"),
|
||||
@ApiResponse(code = 200, message = "Unknown Error", response = ErrorResponseDefault.class) })
|
||||
@RequestMapping(value = "/user-java/{userID}",
|
||||
consumes = { "application/json" },
|
||||
method = RequestMethod.PATCH)
|
||||
ResponseEntity<Profile> updateUser(@ApiParam(value = "User's unique ID",required=true) @PathVariable("userID") String userID,@ApiParam(value = "Details of the profile" ,required=true ) @Valid @RequestBody Profile profile);
|
||||
|
||||
@ApiOperation(value = "", nickname = "userPOST", notes = "Declares and creates a new profile", response = Profile.class, tags={ })
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(code = 201, message = "Creation successful", response = Profile.class),
|
||||
@ApiResponse(code = 200, message = "An error occurred", response = InlineResponseDefault.class) })
|
||||
@RequestMapping(value = "/user-java/{userID}",
|
||||
consumes = { "application/json" },
|
||||
method = RequestMethod.POST)
|
||||
ResponseEntity<Profile> userPOST(@ApiParam(value = "User's unique ID",required=true) @PathVariable("userID") String userID,@ApiParam(value = "Details of the profile" ,required=true ) @Valid @RequestBody Profile profile);
|
||||
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package io.swagger.api;
|
||||
|
||||
import io.swagger.model.Profile;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.swagger.annotations.*;
|
||||
import io.swagger.repository.UserRepositoryService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-08-03T19:26:46.543Z")
|
||||
|
||||
@Controller
|
||||
public class UserApiController implements UserApi {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(UserApiController.class);
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
private HttpServletRequest request;
|
||||
|
||||
@Autowired
|
||||
UserRepositoryService userRepositoryService;
|
||||
|
||||
@org.springframework.beans.factory.annotation.Autowired
|
||||
public UserApiController(ObjectMapper objectMapper, HttpServletRequest request) {
|
||||
this.objectMapper = objectMapper;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public ResponseEntity<Profile> updateUser(@ApiParam(value = "User's unique ID",required=true) @PathVariable("userID") String userID,@ApiParam(value = "Details of the profile" ,required=true ) @Valid @RequestBody Profile profile) {
|
||||
String accept = request.getHeader("Accept");
|
||||
if (accept != null && accept.contains("application/json")) {
|
||||
try {
|
||||
profile.setId(userID);
|
||||
Profile updatedUser = userRepositoryService.update(profile);
|
||||
return new ResponseEntity<Profile>(updatedUser, HttpStatus.OK);
|
||||
} catch (Exception e) {
|
||||
log.error("Error updating user profile", e.getMessage());
|
||||
return new ResponseEntity<Profile>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
return new ResponseEntity<Profile>(HttpStatus.NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
public ResponseEntity<Profile> userPOST(@ApiParam(value = "User's unique ID",required=true) @PathVariable("userID") String userID,@ApiParam(value = "Details of the profile" ,required=true ) @Valid @RequestBody Profile profile) {
|
||||
String accept = request.getHeader("Accept");
|
||||
if (accept != null && accept.contains("application/json")) {
|
||||
try {
|
||||
return new ResponseEntity<Profile>(userRepositoryService.save(profile), HttpStatus.OK);
|
||||
} catch (Exception e) {
|
||||
log.error("Couldn't create new profile", e.getMessage());
|
||||
return new ResponseEntity<Profile>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
return new ResponseEntity<Profile>(HttpStatus.NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
}
|
17
apis/user-java/src/main/java/io/swagger/api/VersionApi.java
Normal file
17
apis/user-java/src/main/java/io/swagger/api/VersionApi.java
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
package io.swagger.api;
|
||||
|
||||
import io.swagger.annotations.*;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-08-03T19:26:46.543Z")
|
||||
|
||||
@Api(value = "version", description = "the version API")
|
||||
public interface VersionApi {
|
||||
@ApiOperation(value = "", nickname = "versionGet", notes = "Returns version for systems looking to identify the current API version or tag", response = String.class, tags={ })
|
||||
@RequestMapping(value = "/version/user-java",
|
||||
produces = { "text/plain" },
|
||||
method = RequestMethod.GET)
|
||||
ResponseEntity<String> versionGet();
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package io.swagger.api;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-08-03T19:26:46.543Z")
|
||||
|
||||
@Controller
|
||||
public class VersionApiController implements VersionApi {
|
||||
private static final Logger log = LoggerFactory.getLogger(VersionApiController.class);
|
||||
private final HttpServletRequest request;
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@org.springframework.beans.factory.annotation.Autowired
|
||||
public VersionApiController(ObjectMapper objectMapper, HttpServletRequest request) {
|
||||
this.objectMapper = objectMapper;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<String> versionGet() {
|
||||
String version = System.getenv("APP_VERSION");
|
||||
return new ResponseEntity<String>(version, HttpStatus.OK);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,232 @@
|
||||
package io.swagger.configuration;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonTokenId;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.datatype.threetenbp.DateTimeUtils;
|
||||
import com.fasterxml.jackson.datatype.threetenbp.DecimalUtils;
|
||||
import com.fasterxml.jackson.datatype.threetenbp.deser.ThreeTenDateTimeDeserializerBase;
|
||||
import com.fasterxml.jackson.datatype.threetenbp.function.BiFunction;
|
||||
import com.fasterxml.jackson.datatype.threetenbp.function.Function;
|
||||
import org.threeten.bp.DateTimeException;
|
||||
import org.threeten.bp.Instant;
|
||||
import org.threeten.bp.OffsetDateTime;
|
||||
import org.threeten.bp.ZoneId;
|
||||
import org.threeten.bp.ZonedDateTime;
|
||||
import org.threeten.bp.format.DateTimeFormatter;
|
||||
import org.threeten.bp.temporal.Temporal;
|
||||
import org.threeten.bp.temporal.TemporalAccessor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Deserializer for ThreeTen temporal {@link Instant}s, {@link OffsetDateTime}, and {@link ZonedDateTime}s.
|
||||
* Adapted from the jackson threetenbp InstantDeserializer to add support for deserializing rfc822 format.
|
||||
*
|
||||
* @author Nick Williams
|
||||
*/
|
||||
public class CustomInstantDeserializer<T extends Temporal>
|
||||
extends ThreeTenDateTimeDeserializerBase<T> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final CustomInstantDeserializer<Instant> INSTANT = new CustomInstantDeserializer<Instant>(
|
||||
Instant.class, DateTimeFormatter.ISO_INSTANT,
|
||||
new Function<TemporalAccessor, Instant>() {
|
||||
@Override
|
||||
public Instant apply(TemporalAccessor temporalAccessor) {
|
||||
return Instant.from(temporalAccessor);
|
||||
}
|
||||
},
|
||||
new Function<FromIntegerArguments, Instant>() {
|
||||
@Override
|
||||
public Instant apply(FromIntegerArguments a) {
|
||||
return Instant.ofEpochMilli(a.value);
|
||||
}
|
||||
},
|
||||
new Function<FromDecimalArguments, Instant>() {
|
||||
@Override
|
||||
public Instant apply(FromDecimalArguments a) {
|
||||
return Instant.ofEpochSecond(a.integer, a.fraction);
|
||||
}
|
||||
},
|
||||
null
|
||||
);
|
||||
|
||||
public static final CustomInstantDeserializer<OffsetDateTime> OFFSET_DATE_TIME = new CustomInstantDeserializer<OffsetDateTime>(
|
||||
OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME,
|
||||
new Function<TemporalAccessor, OffsetDateTime>() {
|
||||
@Override
|
||||
public OffsetDateTime apply(TemporalAccessor temporalAccessor) {
|
||||
return OffsetDateTime.from(temporalAccessor);
|
||||
}
|
||||
},
|
||||
new Function<FromIntegerArguments, OffsetDateTime>() {
|
||||
@Override
|
||||
public OffsetDateTime apply(FromIntegerArguments a) {
|
||||
return OffsetDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId);
|
||||
}
|
||||
},
|
||||
new Function<FromDecimalArguments, OffsetDateTime>() {
|
||||
@Override
|
||||
public OffsetDateTime apply(FromDecimalArguments a) {
|
||||
return OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId);
|
||||
}
|
||||
},
|
||||
new BiFunction<OffsetDateTime, ZoneId, OffsetDateTime>() {
|
||||
@Override
|
||||
public OffsetDateTime apply(OffsetDateTime d, ZoneId z) {
|
||||
return d.withOffsetSameInstant(z.getRules().getOffset(d.toLocalDateTime()));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
public static final CustomInstantDeserializer<ZonedDateTime> ZONED_DATE_TIME = new CustomInstantDeserializer<ZonedDateTime>(
|
||||
ZonedDateTime.class, DateTimeFormatter.ISO_ZONED_DATE_TIME,
|
||||
new Function<TemporalAccessor, ZonedDateTime>() {
|
||||
@Override
|
||||
public ZonedDateTime apply(TemporalAccessor temporalAccessor) {
|
||||
return ZonedDateTime.from(temporalAccessor);
|
||||
}
|
||||
},
|
||||
new Function<FromIntegerArguments, ZonedDateTime>() {
|
||||
@Override
|
||||
public ZonedDateTime apply(FromIntegerArguments a) {
|
||||
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId);
|
||||
}
|
||||
},
|
||||
new Function<FromDecimalArguments, ZonedDateTime>() {
|
||||
@Override
|
||||
public ZonedDateTime apply(FromDecimalArguments a) {
|
||||
return ZonedDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId);
|
||||
}
|
||||
},
|
||||
new BiFunction<ZonedDateTime, ZoneId, ZonedDateTime>() {
|
||||
@Override
|
||||
public ZonedDateTime apply(ZonedDateTime zonedDateTime, ZoneId zoneId) {
|
||||
return zonedDateTime.withZoneSameInstant(zoneId);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
protected final Function<FromIntegerArguments, T> fromMilliseconds;
|
||||
|
||||
protected final Function<FromDecimalArguments, T> fromNanoseconds;
|
||||
|
||||
protected final Function<TemporalAccessor, T> parsedToValue;
|
||||
|
||||
protected final BiFunction<T, ZoneId, T> adjust;
|
||||
|
||||
protected CustomInstantDeserializer(Class<T> supportedType,
|
||||
DateTimeFormatter parser,
|
||||
Function<TemporalAccessor, T> parsedToValue,
|
||||
Function<FromIntegerArguments, T> fromMilliseconds,
|
||||
Function<FromDecimalArguments, T> fromNanoseconds,
|
||||
BiFunction<T, ZoneId, T> adjust) {
|
||||
super(supportedType, parser);
|
||||
this.parsedToValue = parsedToValue;
|
||||
this.fromMilliseconds = fromMilliseconds;
|
||||
this.fromNanoseconds = fromNanoseconds;
|
||||
this.adjust = adjust == null ? new BiFunction<T, ZoneId, T>() {
|
||||
@Override
|
||||
public T apply(T t, ZoneId zoneId) {
|
||||
return t;
|
||||
}
|
||||
} : adjust;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected CustomInstantDeserializer(CustomInstantDeserializer<T> base, DateTimeFormatter f) {
|
||||
super((Class<T>) base.handledType(), f);
|
||||
parsedToValue = base.parsedToValue;
|
||||
fromMilliseconds = base.fromMilliseconds;
|
||||
fromNanoseconds = base.fromNanoseconds;
|
||||
adjust = base.adjust;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonDeserializer<T> withDateFormat(DateTimeFormatter dtf) {
|
||||
if (dtf == _formatter) {
|
||||
return this;
|
||||
}
|
||||
return new CustomInstantDeserializer<T>(this, dtf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T deserialize(JsonParser parser, DeserializationContext context) throws IOException {
|
||||
//NOTE: Timestamps contain no timezone info, and are always in configured TZ. Only
|
||||
//string values have to be adjusted to the configured TZ.
|
||||
switch (parser.getCurrentTokenId()) {
|
||||
case JsonTokenId.ID_NUMBER_FLOAT: {
|
||||
BigDecimal value = parser.getDecimalValue();
|
||||
long seconds = value.longValue();
|
||||
int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
|
||||
return fromNanoseconds.apply(new FromDecimalArguments(
|
||||
seconds, nanoseconds, getZone(context)));
|
||||
}
|
||||
|
||||
case JsonTokenId.ID_NUMBER_INT: {
|
||||
long timestamp = parser.getLongValue();
|
||||
if (context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)) {
|
||||
return this.fromNanoseconds.apply(new FromDecimalArguments(
|
||||
timestamp, 0, this.getZone(context)
|
||||
));
|
||||
}
|
||||
return this.fromMilliseconds.apply(new FromIntegerArguments(
|
||||
timestamp, this.getZone(context)
|
||||
));
|
||||
}
|
||||
|
||||
case JsonTokenId.ID_STRING: {
|
||||
String string = parser.getText().trim();
|
||||
if (string.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
if (string.endsWith("+0000")) {
|
||||
string = string.substring(0, string.length() - 5) + "Z";
|
||||
}
|
||||
T value;
|
||||
try {
|
||||
TemporalAccessor acc = _formatter.parse(string);
|
||||
value = parsedToValue.apply(acc);
|
||||
if (context.isEnabled(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)) {
|
||||
return adjust.apply(value, this.getZone(context));
|
||||
}
|
||||
} catch (DateTimeException e) {
|
||||
throw _peelDTE(e);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
throw context.mappingException("Expected type float, integer, or string.");
|
||||
}
|
||||
|
||||
private ZoneId getZone(DeserializationContext context) {
|
||||
// Instants are always in UTC, so don't waste compute cycles
|
||||
return (_valueClass == Instant.class) ? null : DateTimeUtils.timeZoneToZoneId(context.getTimeZone());
|
||||
}
|
||||
|
||||
private static class FromIntegerArguments {
|
||||
public final long value;
|
||||
public final ZoneId zoneId;
|
||||
|
||||
private FromIntegerArguments(long value, ZoneId zoneId) {
|
||||
this.value = value;
|
||||
this.zoneId = zoneId;
|
||||
}
|
||||
}
|
||||
|
||||
private static class FromDecimalArguments {
|
||||
public final long integer;
|
||||
public final int fraction;
|
||||
public final ZoneId zoneId;
|
||||
|
||||
private FromDecimalArguments(long integer, int fraction, ZoneId zoneId) {
|
||||
this.integer = integer;
|
||||
this.fraction = fraction;
|
||||
this.zoneId = zoneId;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package io.swagger.configuration;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
/**
|
||||
* Home redirection to swagger api documentation
|
||||
*/
|
||||
@Controller
|
||||
public class HomeController {
|
||||
@RequestMapping(value = "/")
|
||||
public String index() {
|
||||
System.out.println("swagger-ui.html");
|
||||
return "redirect:swagger-ui.html";
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package io.swagger.configuration;
|
||||
|
||||
import com.fasterxml.jackson.datatype.threetenbp.ThreeTenModule;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.threeten.bp.Instant;
|
||||
import org.threeten.bp.OffsetDateTime;
|
||||
import org.threeten.bp.ZonedDateTime;
|
||||
|
||||
@Configuration
|
||||
public class JacksonConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ThreeTenModule.class)
|
||||
ThreeTenModule threeTenModule() {
|
||||
ThreeTenModule module = new ThreeTenModule();
|
||||
module.addDeserializer(Instant.class, CustomInstantDeserializer.INSTANT);
|
||||
module.addDeserializer(OffsetDateTime.class, CustomInstantDeserializer.OFFSET_DATE_TIME);
|
||||
module.addDeserializer(ZonedDateTime.class, CustomInstantDeserializer.ZONED_DATE_TIME);
|
||||
return module;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package io.swagger.configuration;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.service.Contact;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-08-03T19:26:46.543Z")
|
||||
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
@EnableWebMvc
|
||||
public class SwaggerDocumentationConfig extends WebMvcConfigurerAdapter {
|
||||
|
||||
ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title("My Driving User Java API")
|
||||
.description("API for the user in the My Driving example app. https://github.com/Azure-Samples/openhack-devops-team")
|
||||
.license("")
|
||||
.licenseUrl("http://unlicense.org")
|
||||
.termsOfServiceUrl("")
|
||||
.version("0.1.0")
|
||||
.contact(new Contact("","", ""))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket customImplementation(){
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("io.swagger.api"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.pathMapping("/")
|
||||
.directModelSubstitute(org.threeten.bp.LocalDate.class, java.sql.Date.class)
|
||||
.directModelSubstitute(org.threeten.bp.OffsetDateTime.class, java.util.Date.class)
|
||||
.apiInfo(apiInfo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addViewControllers(ViewControllerRegistry registry) {
|
||||
registry.addRedirectViewController("/docs/user-java/api-docs", "/api-docs").setKeepQueryParams(true);
|
||||
registry.addRedirectViewController("/docs/user-java/swagger-resources/configuration/ui","/swagger-resources/configuration/ui");
|
||||
registry.addRedirectViewController("/docs/user-java/swagger-resources/configuration/security","/swagger-resources/configuration/security");
|
||||
registry.addRedirectViewController("/docs/user-java/swagger-resources", "/swagger-resources");
|
||||
registry.addRedirectViewController("/docs/user-java", "/docs/user-java/swagger-ui.html");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
registry.addResourceHandler("/docs/user-java/**").addResourceLocations("classpath:/META-INF/resources/");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package io.swagger.model;
|
||||
|
||||
import java.util.Objects;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* ErrorResponseDefault
|
||||
*/
|
||||
@Validated
|
||||
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-08-03T19:26:46.543Z")
|
||||
|
||||
public class ErrorResponseDefault {
|
||||
@JsonProperty("status")
|
||||
private Integer status = null;
|
||||
|
||||
@JsonProperty("message")
|
||||
private String message = null;
|
||||
|
||||
public ErrorResponseDefault status(Integer status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error code (if available)
|
||||
* @return status
|
||||
**/
|
||||
@ApiModelProperty(value = "Error code (if available)")
|
||||
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public ErrorResponseDefault message(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error Message
|
||||
* @return message
|
||||
**/
|
||||
@ApiModelProperty(value = "Error Message")
|
||||
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ErrorResponseDefault errorResponseDefault = (ErrorResponseDefault) o;
|
||||
return Objects.equals(this.status, errorResponseDefault.status) &&
|
||||
Objects.equals(this.message, errorResponseDefault.message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(status, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("class ErrorResponseDefault {\n");
|
||||
|
||||
sb.append(" status: ").append(toIndentedString(status)).append("\n");
|
||||
sb.append(" message: ").append(toIndentedString(message)).append("\n");
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given object to string with each line indented by 4 spaces
|
||||
* (except the first line).
|
||||
*/
|
||||
private String toIndentedString(java.lang.Object o) {
|
||||
if (o == null) {
|
||||
return "null";
|
||||
}
|
||||
return o.toString().replace("\n", "\n ");
|
||||
}
|
||||
}
|
||||
|
102
apis/user-java/src/main/java/io/swagger/model/Healthcheck.java
Normal file
102
apis/user-java/src/main/java/io/swagger/model/Healthcheck.java
Normal file
@ -0,0 +1,102 @@
|
||||
package io.swagger.model;
|
||||
|
||||
import java.util.Objects;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* Healthcheck
|
||||
*/
|
||||
@Validated
|
||||
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-08-03T19:26:46.543Z")
|
||||
|
||||
public class Healthcheck {
|
||||
@JsonProperty("message")
|
||||
private String message = null;
|
||||
|
||||
@JsonProperty("status")
|
||||
private String status = null;
|
||||
|
||||
public Healthcheck message(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return message
|
||||
**/
|
||||
@ApiModelProperty(value = "")
|
||||
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Healthcheck status(String status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return status
|
||||
**/
|
||||
@ApiModelProperty(value = "")
|
||||
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Healthcheck healthcheck = (Healthcheck) o;
|
||||
return Objects.equals(this.message, healthcheck.message) &&
|
||||
Objects.equals(this.status, healthcheck.status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(message, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("class Healthcheck {\n");
|
||||
|
||||
sb.append(" message: ").append(toIndentedString(message)).append("\n");
|
||||
sb.append(" status: ").append(toIndentedString(status)).append("\n");
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given object to string with each line indented by 4 spaces
|
||||
* (except the first line).
|
||||
*/
|
||||
private String toIndentedString(java.lang.Object o) {
|
||||
if (o == null) {
|
||||
return "null";
|
||||
}
|
||||
return o.toString().replace("\n", "\n ");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,105 @@
|
||||
package io.swagger.model;
|
||||
|
||||
import java.util.Objects;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import javax.validation.constraints.*;
|
||||
|
||||
/**
|
||||
* InlineResponseDefault
|
||||
*/
|
||||
@Validated
|
||||
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-08-03T19:26:46.543Z")
|
||||
|
||||
public class InlineResponseDefault {
|
||||
@JsonProperty("status")
|
||||
private Integer status = null;
|
||||
|
||||
@JsonProperty("message")
|
||||
private String message = null;
|
||||
|
||||
public InlineResponseDefault status(Integer status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status
|
||||
* @return status
|
||||
**/
|
||||
@ApiModelProperty(required = true, value = "")
|
||||
@NotNull
|
||||
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public InlineResponseDefault message(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get message
|
||||
* @return message
|
||||
**/
|
||||
@ApiModelProperty(required = true, value = "")
|
||||
@NotNull
|
||||
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
InlineResponseDefault inlineResponseDefault = (InlineResponseDefault) o;
|
||||
return Objects.equals(this.status, inlineResponseDefault.status) &&
|
||||
Objects.equals(this.message, inlineResponseDefault.message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(status, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("class InlineResponseDefault {\n");
|
||||
|
||||
sb.append(" status: ").append(toIndentedString(status)).append("\n");
|
||||
sb.append(" message: ").append(toIndentedString(message)).append("\n");
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given object to string with each line indented by 4 spaces
|
||||
* (except the first line).
|
||||
*/
|
||||
private String toIndentedString(java.lang.Object o) {
|
||||
if (o == null) {
|
||||
return "null";
|
||||
}
|
||||
return o.toString().replace("\n", "\n ");
|
||||
}
|
||||
}
|
||||
|
BIN
apis/user-java/src/main/java/io/swagger/model/Profile.java
Normal file
BIN
apis/user-java/src/main/java/io/swagger/model/Profile.java
Normal file
Binary file not shown.
@ -0,0 +1,9 @@
|
||||
package io.swagger.repository;
|
||||
|
||||
import io.swagger.model.Profile;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface UserRepository extends JpaRepository<Profile, String> {
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package io.swagger.repository;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.swagger.model.Profile;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
@Service
|
||||
public class UserRepositoryService {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(UserRepositoryService.class);
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
|
||||
|
||||
public Profile update(Profile updateEntry) {
|
||||
long start = System.currentTimeMillis();
|
||||
Preconditions.checkNotNull(updateEntry, "User profile cannot be null");
|
||||
|
||||
LOGGER.info("Updating user profile for user=%s", updateEntry.getId());
|
||||
Profile existingUser = findOne(updateEntry.getId());
|
||||
if (existingUser == null) {
|
||||
throw new NullPointerException("Unable to locate user");
|
||||
}
|
||||
|
||||
existingUser.setTotalTrips(updateEntry.getTotalTrips() == null? existingUser.getTotalTrips() : updateEntry.getTotalTrips());
|
||||
existingUser.setTotalDistance(updateEntry.getTotalDistance() == null ? existingUser.getTotalTrips() : updateEntry.getTotalDistance());
|
||||
existingUser.setTotalTime(updateEntry.getTotalTime() == null ? existingUser.getTotalTime() : updateEntry.getTotalTime());
|
||||
existingUser.setHardStops(updateEntry.getHardStops() == null ? existingUser.getHardStops() : updateEntry.getHardStops());
|
||||
existingUser.setHardAccelerations(updateEntry.getHardAccelerations() == null ? existingUser.getHardAccelerations() : updateEntry.getHardAccelerations());
|
||||
existingUser.setFuelConsumption(updateEntry.getFuelConsumption() == null ? existingUser.getFuelConsumption() : updateEntry.getFuelConsumption());
|
||||
existingUser.setMaxSpeed(updateEntry.getMaxSpeed() == null ? existingUser.getMaxSpeed() : updateEntry.getMaxSpeed());
|
||||
existingUser.setRating(updateEntry.getRating() == null ? existingUser.getRating() : updateEntry.getRating());
|
||||
existingUser.setRating(updateEntry.getRanking() == null ? existingUser.getRanking() : updateEntry.getRanking());
|
||||
|
||||
Profile userProfile = save(existingUser);
|
||||
long end = System.currentTimeMillis();
|
||||
long timeElapsed = end - start;
|
||||
|
||||
LOGGER.info("Update method called: on {} and response time in ms: {}", timestamp, timeElapsed);
|
||||
return userProfile;
|
||||
}
|
||||
|
||||
public Profile save(Profile newProfile) {
|
||||
Preconditions.checkNotNull(newProfile, "User profile cannot be null");
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
Profile userProfile = userRepository.save(newProfile);
|
||||
long end = System.currentTimeMillis();
|
||||
long timeElapsed = end - start;
|
||||
|
||||
LOGGER.info("Save method called: on {} and response time in ms: {}", timestamp, timeElapsed);
|
||||
return userProfile;
|
||||
}
|
||||
|
||||
private Profile findOne(String Id) {
|
||||
Preconditions.checkNotNull(Id, "User Id cannot be null");
|
||||
return userRepository.findOne(Id);
|
||||
}
|
||||
|
||||
}
|
10
apis/user-java/src/main/resources/application.properties
Normal file
10
apis/user-java/src/main/resources/application.properties
Normal file
@ -0,0 +1,10 @@
|
||||
springfox.documentation.swagger.v2.path=/api-docs
|
||||
server.contextPath=/api
|
||||
server.port=8080
|
||||
spring.jackson.date-format=io.swagger.RFC3339DateFormat
|
||||
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
|
||||
spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
spring.jpa.database-platform=org.hibernate.dialect.SQLServer2012Dialect
|
||||
spring.datasource.url=jdbc:sqlserver://${SQL_SERVER};databaseName=${SQL_DBNAME};trustServerCertificate=true;selectMethod=cursor
|
||||
spring.datasource.username=${SQL_USER}
|
||||
spring.datasource.password=${SQL_PASSWORD}
|
119
apis/user-java/src/test/java/UserApiControllerTest.java
Normal file
119
apis/user-java/src/test/java/UserApiControllerTest.java
Normal file
@ -0,0 +1,119 @@
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.swagger.api.UserApiController;
|
||||
import io.swagger.model.Profile;
|
||||
import io.swagger.repository.UserRepositoryService;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class UserApiControllerTest {
|
||||
|
||||
public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
|
||||
|
||||
@InjectMocks
|
||||
private UserApiController userApiController;
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
private Profile profile;
|
||||
|
||||
@Mock
|
||||
HttpServletRequest httpServletRequest;
|
||||
|
||||
@Mock
|
||||
UserRepositoryService userRepositoryService;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
||||
// this must be called for the @Mock annotations above to be processed
|
||||
// and for the mock service to be injected into the controller under
|
||||
// test.
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(userApiController).build();
|
||||
profile = new Profile();
|
||||
profile.setFirstName("test");
|
||||
profile.setUserId("userId");
|
||||
profile.setRanking(1);
|
||||
profile.setTotalDistance(1000f);
|
||||
profile.setId("2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSave() throws Exception {
|
||||
when(httpServletRequest.getHeader("Accept")).thenReturn("accept,application/json;charset=UTF-8");
|
||||
when(userRepositoryService.save(profile)).thenReturn(profile);
|
||||
mockMvc.perform(
|
||||
post("/user-java/2")
|
||||
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.content(convertObjectToJsonBytes(profile))
|
||||
)
|
||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
|
||||
verify(userRepositoryService, times(1)).save(profile);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSave_shouldNotImplemented() throws Exception {
|
||||
mockMvc.perform(
|
||||
post("/user-java/2")
|
||||
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.content(convertObjectToJsonBytes(profile))
|
||||
)
|
||||
.andExpect(MockMvcResultMatchers.status().is5xxServerError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdate_shouldNotImplemented() throws Exception {
|
||||
mockMvc.perform(
|
||||
patch("/user-java/2")
|
||||
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.content(convertObjectToJsonBytes(profile))
|
||||
)
|
||||
.andExpect(MockMvcResultMatchers.status().is5xxServerError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdate() throws Exception {
|
||||
profile.setRanking(2);
|
||||
profile.setTotalDistance(2500F);
|
||||
when(httpServletRequest.getHeader("Accept")).thenReturn("accept,application/json;charset=UTF-8");
|
||||
when(userRepositoryService.update(profile)).thenReturn(profile);
|
||||
mockMvc.perform(
|
||||
patch("/user-java/2")
|
||||
.contentType(APPLICATION_JSON_UTF8)
|
||||
.content(convertObjectToJsonBytes(profile)))
|
||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
|
||||
verify(userRepositoryService, times(1)).update(profile);
|
||||
|
||||
}
|
||||
|
||||
public static byte[] convertObjectToJsonBytes(Object object) throws IOException {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
return mapper.writeValueAsBytes(object);
|
||||
}
|
||||
}
|
84
apis/user-java/src/test/java/UserRepositoryServiceTest.java
Normal file
84
apis/user-java/src/test/java/UserRepositoryServiceTest.java
Normal file
@ -0,0 +1,84 @@
|
||||
import io.swagger.model.Profile;
|
||||
import io.swagger.repository.UserRepository;
|
||||
import io.swagger.repository.UserRepositoryService;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class UserRepositoryServiceTest {
|
||||
|
||||
private static final String USER_ID = "test1";
|
||||
private static final String ID = "1234";
|
||||
private static final String FIRST_NAME = "test";
|
||||
private static final String LAST_NAME = "lastname";
|
||||
private static final int RANKING = 1;
|
||||
private static final float TOTAL_DISTANCE = 1000;
|
||||
|
||||
private Profile profile;
|
||||
|
||||
private Integer ranking = 2;
|
||||
private Float distance = 2000F;
|
||||
|
||||
@Mock
|
||||
UserRepository userRepository;
|
||||
|
||||
@InjectMocks
|
||||
UserRepositoryService userRepositoryService;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
profile = new Profile();
|
||||
profile.setUserId(USER_ID);
|
||||
profile.setId(ID);
|
||||
profile.setFirstName(FIRST_NAME);
|
||||
profile.setLastName(LAST_NAME);
|
||||
profile.setRanking(RANKING);
|
||||
profile.setTotalDistance(TOTAL_DISTANCE);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
profile = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSave() {
|
||||
when(userRepository.save(any(Profile.class))).thenReturn(profile);
|
||||
assertNotNull(userRepositoryService.save(profile));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdate() {
|
||||
when(userRepository.findOne(ID)).thenReturn(profile);
|
||||
profile.setRanking(ranking);
|
||||
profile.setTotalDistance(distance);
|
||||
when(userRepository.save(profile)).thenReturn(profile);
|
||||
Profile updated = userRepositoryService.update(profile);
|
||||
assertEquals(ranking, updated.getRanking());
|
||||
assertEquals(distance, updated.getTotalDistance());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdate_shouldThrowException() {
|
||||
when(userRepository.findOne(USER_ID)).thenReturn(null);
|
||||
try {
|
||||
userRepositoryService.update(profile);
|
||||
fail("Unable to locate user");
|
||||
|
||||
}catch (NullPointerException e) {
|
||||
assertEquals("Unable to locate user", e.getMessage());
|
||||
}
|
||||
verify(userRepository, never()).save(profile);
|
||||
}
|
||||
}
|
1
apis/userprofile/.dockerignore
Normal file
1
apis/userprofile/.dockerignore
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
2
apis/userprofile/.eslintignore
Normal file
2
apis/userprofile/.eslintignore
Normal file
@ -0,0 +1,2 @@
|
||||
coverage
|
||||
templates
|
25
apis/userprofile/.eslintrc
Normal file
25
apis/userprofile/.eslintrc
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"rules": {
|
||||
"indent": [
|
||||
2,
|
||||
4
|
||||
],
|
||||
"quotes": [
|
||||
2,
|
||||
"single"
|
||||
],
|
||||
"linebreak-style": [
|
||||
2,
|
||||
"unix"
|
||||
],
|
||||
"semi": [
|
||||
2,
|
||||
"always"
|
||||
]
|
||||
},
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": "eslint:recommended"
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user