Initial commit
All checks were successful
Build / build (push) Successful in 57s

This commit is contained in:
2025-11-08 14:26:53 +00:00
commit 018d8e184e
26 changed files with 1251 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
using System.Collections.Generic;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Testing;
using TestAspNetCore.Infra;
namespace TestAspNetCore.Core.Jobs;
public class JobApiTests : IClassFixture<TestFixture>
{
private readonly HttpClient client;
public JobApiTests(TestFixture fixture)
{
client = fixture.Client;
}
[Fact]
public async Task Base()
{
// Create
var createRequest = new CreateJobEndpoint.Request(
"Client name", "Pickup", "Dropoff"
);
var createCall = await client.PostAsJsonAsync(
JobApi.Prefix,
createRequest,
cancellationToken: TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.OK, createCall.StatusCode);
var createResponse = await createCall.Content
.ReadFromJsonAsync<CreateJobEndpoint.Response>(
cancellationToken: TestContext.Current.CancellationToken
);
Assert.NotNull(createResponse);
// Get
var getListCall = await client.GetAsync(
JobApi.Prefix,
TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.OK, getListCall.StatusCode);
var getCall = await client.GetAsync(
$"{JobApi.Prefix}/{createResponse.Id}",
TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.OK, getCall.StatusCode);
// Update
var updateRequest = new UpdateJobEndpoint.Request(
"Client name", "Pickup update", "Dropoff update");
var updateCall = await client.PutAsJsonAsync(
$"{JobApi.Prefix}/{createResponse.Id}", updateRequest,
cancellationToken: TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.OK, updateCall.StatusCode);
// Delete
var deleteCall = await client.DeleteAsync(
$"{JobApi.Prefix}/{createResponse.Id}",
TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.OK, deleteCall.StatusCode);
// Not found
var deleteCallNotFound = await client.DeleteAsync(
$"{JobApi.Prefix}/{createResponse.Id}",
TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.NotFound, deleteCallNotFound.StatusCode);
var getCallNotFound = await client.GetAsync(
$"{JobApi.Prefix}/{createResponse.Id}",
TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.NotFound, getCallNotFound.StatusCode);
}
}

View File

@@ -0,0 +1,150 @@
using TestAspNetCore.Infra;
using TestAspNetCore.Utils;
namespace TestAspNetCore.Core.Jobs;
public static class JobApi
{
public const string Prefix = "/api/jobs";
public static void MapEndpoints(IEndpointRouteBuilder endpoints)
{
var group = endpoints.MapGroup(Prefix)
.WithTags("Jobs");
group.MapPost("/", CreateJobEndpoint.Handle)
.WithSummary("Create a job")
.WithRequestValidation<CreateJobEndpoint.Request>();
group.MapGet("/", GetJobListEndpoint.Handle)
.WithSummary("Get job list");
group.MapGet("/{id}", GetJobEndpoint.Handle)
.WithSummary("Get job by id");
group.MapPut("/{id}", UpdateJobEndpoint.Handle)
.WithSummary("Update job by id")
.WithRequestValidation<UpdateJobEndpoint.Request>();
group.MapDelete("/{id}", DeleteJobEndpoint.Handle)
.WithSummary("Delete job by id");
}
}
public static class CreateJobEndpoint
{
public record Request(
string ClientName,
string Pickup,
string Dropoff
);
public record Response(int Id);
public class RequestValidator : AbstractValidator<Request>
{
public RequestValidator()
{
RuleFor(x => x.ClientName).NotEmpty().MaximumLength(250);
RuleFor(x => x.Pickup).NotEmpty().MaximumLength(120);
RuleFor(x => x.Dropoff).NotEmpty().MaximumLength(120);
}
}
public static async Task<Ok<Response>> Handle(Request request, AppDbContext db, CancellationToken ct)
{
var row = new Job
{
ClientName = request.ClientName,
Pickup = request.Pickup,
Dropoff = request.Dropoff,
};
await db.Jobs.AddAsync(row, ct);
await db.SaveChangesAsync(ct);
var response = new Response(row.Id);
return TypedResults.Ok(response);
}
}
public static class GetJobListEndpoint
{
public static async Task<Ok<Job[]>> Handle(AppDbContext db, CancellationToken ct)
{
var results = await db.Jobs.ToArrayAsync(ct);
return TypedResults.Ok(results);
}
}
public static class GetJobEndpoint
{
public static async Task<Results<Ok<Job>, NotFound>> Handle(
int id,
AppDbContext db,
CancellationToken ct)
{
var result = await db.Jobs
.Where(x => x.Id == id)
.SingleOrDefaultAsync(ct);
return result is null
? TypedResults.NotFound()
: TypedResults.Ok(result);
}
}
public static class UpdateJobEndpoint
{
public record Request(
string ClientName,
string Pickup,
string Dropoff
);
public class RequestValidator : AbstractValidator<Request>
{
public RequestValidator()
{
RuleFor(x => x.ClientName).NotEmpty().MaximumLength(250);
RuleFor(x => x.Pickup).NotEmpty().MaximumLength(120);
RuleFor(x => x.Dropoff).NotEmpty().MaximumLength(120);
}
}
public static async Task<Results<Ok, NotFound>> Handle(
int id,
Request request,
AppDbContext db,
CancellationToken ct)
{
var result = await db.Jobs
.Where(x => x.Id == id)
.SingleOrDefaultAsync(ct);
if (result == null)
{
return TypedResults.NotFound();
}
result.ClientName = request.ClientName;
result.Pickup = request.Pickup;
result.Dropoff = request.Dropoff;
result.LastUpdatedAtUtc = DateTime.UtcNow;
await db.SaveChangesAsync(ct);
return TypedResults.Ok();
}
}
public static class DeleteJobEndpoint
{
public static async Task<Results<Ok, NotFound>> Handle(
int id,
AppDbContext db,
CancellationToken ct)
{
var rowsDeleted = await db.Jobs
.Where(x => x.Id == id)
.ExecuteDeleteAsync(ct);
return rowsDeleted == 1
? TypedResults.Ok()
: TypedResults.NotFound();
}
}

View File

@@ -0,0 +1,78 @@
using System.Collections.Generic;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Testing;
using TestAspNetCore.Infra;
namespace TestAspNetCore.Core.Users;
public class UserApiTests : IClassFixture<TestFixture>
{
private readonly HttpClient client;
public UserApiTests(TestFixture fixture)
{
client = fixture.Client;
}
[Fact]
public async Task Base()
{
// Create
var createRequest = new CreateUserEndpoint.Request("Test");
var createCall = await client.PostAsJsonAsync(
UserApi.Prefix,
createRequest,
cancellationToken: TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.OK, createCall.StatusCode);
var createResponse = await createCall.Content
.ReadFromJsonAsync<CreateUserEndpoint.Response>(cancellationToken: TestContext.Current.CancellationToken);
Assert.NotNull(createResponse);
// Get
var getListCall = await client.GetAsync(
UserApi.Prefix,
TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.OK, getListCall.StatusCode);
var getCall = await client.GetAsync(
$"{UserApi.Prefix}/{createResponse.Id}",
TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.OK, getCall.StatusCode);
// Update
var updateRequest = new UpdateUserEndpoint.Request("Test");
var updateCall = await client.PutAsJsonAsync(
$"{UserApi.Prefix}/{createResponse.Id}",
updateRequest,
cancellationToken: TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.OK, updateCall.StatusCode);
// Delete
var deleteCall = await client.DeleteAsync(
$"{UserApi.Prefix}/{createResponse.Id}",
TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.OK, deleteCall.StatusCode);
// Not found
var deleteCallNotFound = await client.DeleteAsync(
$"{UserApi.Prefix}/{createResponse.Id}",
TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.NotFound, deleteCallNotFound.StatusCode);
var getCallNotFound = await client.GetAsync(
$"{UserApi.Prefix}/{createResponse.Id}",
TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.NotFound, getCallNotFound.StatusCode);
}
}

View File

@@ -0,0 +1,136 @@
using Microsoft.Extensions.Options;
using TestAspNetCore.Infra;
using TestAspNetCore.Utils;
namespace TestAspNetCore.Core.Users;
public static class UserApi
{
public const string Prefix = "/api/users";
public static void MapEndpoints(IEndpointRouteBuilder endpoints)
{
var group = endpoints.MapGroup(Prefix)
.WithTags("Users");
group.MapPost("/", CreateUserEndpoint.Handle)
.WithSummary("Create a user")
.WithRequestValidation<CreateUserEndpoint.Request>();
group.MapGet("/", GetUserListEndpoint.Handle)
.WithSummary("Get user list");
group.MapGet("/{id}", GetUserEndpoint.Handle)
.WithSummary("Get user by id");
group.MapPut("/{id}", UpdateUserEndpoint.Handle)
.WithSummary("Update user by id")
.WithRequestValidation<UpdateUserEndpoint.Request>();
group.MapDelete("/{id}", DeleteUserEndpoint.Handle)
.WithSummary("Delete user by id");
}
}
public static class CreateUserEndpoint
{
public record Request(string UserName);
public class RequestValidator : AbstractValidator<Request>
{
public RequestValidator()
{
RuleFor(x => x.UserName).NotEmpty().MaximumLength(250);
}
}
public record Response(int Id);
public static async Task<Ok<Response>> Handle(Request request, AppDbContext db, CancellationToken ct)
{
var row = new User
{
Username = request.UserName,
DisplayName = request.UserName,
Password = string.Empty
};
await db.Users.AddAsync(row, ct);
await db.SaveChangesAsync(ct);
var response = new Response(row.Id);
return TypedResults.Ok(response);
}
}
public static class GetUserListEndpoint
{
public static async Task<Ok<User[]>> Handle(AppDbContext db, CancellationToken ct)
{
var results = await db.Users.ToArrayAsync(ct);
return TypedResults.Ok(results);
}
}
public static class GetUserEndpoint
{
public static async Task<Results<Ok<User>, NotFound>> Handle(
int id,
AppDbContext db,
CancellationToken ct)
{
var result = await db.Users
.Where(x => x.Id == id)
.SingleOrDefaultAsync(ct);
return result is null
? TypedResults.NotFound()
: TypedResults.Ok(result);
}
}
public static class UpdateUserEndpoint
{
public record Request(string DisplayName);
public class RequestValidator : AbstractValidator<Request>
{
public RequestValidator()
{
RuleFor(x => x.DisplayName).NotEmpty().MaximumLength(250);
}
}
public static async Task<Results<Ok, NotFound>> Handle(
int id,
Request request,
AppDbContext db,
CancellationToken ct)
{
var result = await db.Users
.Where(x => x.Id == id)
.SingleOrDefaultAsync(ct);
if (result == null)
{
return TypedResults.NotFound();
}
result.DisplayName = request.DisplayName;
await db.SaveChangesAsync(ct);
return TypedResults.Ok();
}
}
public static class DeleteUserEndpoint
{
public static async Task<Results<Ok, NotFound>> Handle(
int id,
AppDbContext db,
CancellationToken ct)
{
var rowsDeleted = await db.Users
.Where(x => x.Id == id)
.ExecuteDeleteAsync(ct);
return rowsDeleted == 1
? TypedResults.Ok()
: TypedResults.NotFound();
}
}

View File

@@ -0,0 +1,80 @@
using System.Collections.Generic;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Testing;
using TestAspNetCore.Infra;
namespace TestAspNetCore.Core.Vehicles;
public class VehicleApiTests : IClassFixture<TestFixture>
{
private readonly HttpClient client;
public VehicleApiTests(TestFixture fixture)
{
client = fixture.Client;
}
[Fact]
public async Task Base()
{
// Create
var createRequest = new CreateVehicleEndpoint.Request("Test");
var createCall = await client.PostAsJsonAsync(
VehicleApi.Prefix,
createRequest,
cancellationToken: TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.OK, createCall.StatusCode);
var createResponse = await createCall.Content
.ReadFromJsonAsync<CreateVehicleEndpoint.Response>(
cancellationToken: TestContext.Current.CancellationToken
);
Assert.NotNull(createResponse);
// Get
var getListCall = await client.GetAsync(
VehicleApi.Prefix,
TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.OK, getListCall.StatusCode);
var getCall = await client.GetAsync(
$"{VehicleApi.Prefix}/{createResponse.Id}",
TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.OK, getCall.StatusCode);
// Update
var updateRequest = new UpdateVehicleEndpoint.Request("Test");
var updateCall = await client.PutAsJsonAsync(
$"{VehicleApi.Prefix}/{createResponse.Id}",
updateRequest,
cancellationToken: TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.OK, updateCall.StatusCode);
// Delete
var deleteCall = await client.DeleteAsync(
$"{VehicleApi.Prefix}/{createResponse.Id}",
TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.OK, deleteCall.StatusCode);
// Not found
var deleteCallNotFound = await client.DeleteAsync(
$"{VehicleApi.Prefix}/{createResponse.Id}",
TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.NotFound, deleteCallNotFound.StatusCode);
var getCallNotFound = await client.GetAsync(
$"{VehicleApi.Prefix}/{createResponse.Id}",
TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.NotFound, getCallNotFound.StatusCode);
}
}

View File

@@ -0,0 +1,136 @@
using TestAspNetCore.Infra;
using TestAspNetCore.Utils;
namespace TestAspNetCore.Core.Vehicles;
public static class VehicleApi
{
public const string Prefix = "/api/vehicles";
public static void MapEndpoints(IEndpointRouteBuilder endpoints)
{
var group = endpoints.MapGroup(Prefix)
.WithTags("Vehicles");
group.MapPost("/", CreateVehicleEndpoint.Handle)
.WithSummary("Create a vehicle")
.WithRequestValidation<CreateVehicleEndpoint.Request>();
group.MapGet("/", GetVehicleListEndpoint.Handle)
.WithSummary("Get vehicle list");
group.MapGet("/{id}", GetVehicleEndpoint.Handle)
.WithSummary("Get vehicle by id");
group.MapPut("/{id}", UpdateVehicleEndpoint.Handle)
.WithSummary("Update vehicle by id")
.WithRequestValidation<UpdateVehicleEndpoint.Request>();
group.MapDelete("/{id}", DeleteVehicleEndpoint.Handle)
.WithSummary("Delete vehicle by id");
}
}
public static class CreateVehicleEndpoint
{
public record Request(string Make);
public record Response(int Id);
public class RequestValidator : AbstractValidator<Request>
{
public RequestValidator()
{
RuleFor(x => x.Make).NotEmpty().MaximumLength(250);
}
}
public static async Task<Ok<Response>> Handle(Request request, AppDbContext db, CancellationToken ct)
{
var row = new Vehicle
{
Make = request.Make,
Model = "",
Year = DateTime.Now.Year
};
await db.Vehicles.AddAsync(row, ct);
await db.SaveChangesAsync(ct);
var response = new Response(row.Id);
return TypedResults.Ok(response);
}
}
public static class GetVehicleListEndpoint
{
public static async Task<Ok<Vehicle[]>> Handle(AppDbContext db, CancellationToken ct)
{
var results = await db.Vehicles.ToArrayAsync(ct);
return TypedResults.Ok(results);
}
}
public static class GetVehicleEndpoint
{
public static async Task<Results<Ok<Vehicle>, NotFound>> Handle(
int id,
AppDbContext db,
CancellationToken ct)
{
var result = await db.Vehicles
.Where(x => x.Id == id)
.SingleOrDefaultAsync(ct);
return result is null
? TypedResults.NotFound()
: TypedResults.Ok(result);
}
}
public static class UpdateVehicleEndpoint
{
public record Request(string Make);
public class RequestValidator : AbstractValidator<Request>
{
public RequestValidator()
{
RuleFor(x => x.Make).NotEmpty().MaximumLength(250);
}
}
public static async Task<Results<Ok, NotFound>> Handle(
int id,
Request request,
AppDbContext db,
CancellationToken ct)
{
var result = await db.Vehicles
.Where(x => x.Id == id)
.SingleOrDefaultAsync(ct);
if (result == null)
{
return TypedResults.NotFound();
}
result.Make = request.Make;
result.LastUpdatedAtUtc = DateTime.UtcNow;
await db.SaveChangesAsync(ct);
return TypedResults.Ok();
}
}
public static class DeleteVehicleEndpoint
{
public static async Task<Results<Ok, NotFound>> Handle(
int id,
AppDbContext db,
CancellationToken ct)
{
var rowsDeleted = await db.Vehicles
.Where(x => x.Id == id)
.ExecuteDeleteAsync(ct);
return rowsDeleted == 1
? TypedResults.Ok()
: TypedResults.NotFound();
}
}

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Testing;
using TestAspNetCore.Infra;
namespace TestAspNetCore.Core.WeatherForecasts;
public class WeatherForecastApiTests : IClassFixture<TestFixture>
{
private readonly HttpClient client;
public WeatherForecastApiTests(TestFixture fixture)
{
client = fixture.Client;
}
[Fact]
public async Task Base()
{
// Get
var getCall = await client.GetAsync(
WeatherForecastApi.Prefix,
TestContext.Current.CancellationToken
);
Assert.Equal(HttpStatusCode.OK, getCall.StatusCode);
}
}

View File

@@ -0,0 +1,39 @@
namespace TestAspNetCore.Core.WeatherForecasts;
public static class WeatherForecastApi
{
public const string Prefix = "/api/weatherforecasts";
public static void MapEndpoints(IEndpointRouteBuilder endpoints)
{
var group = endpoints.MapGroup("/api/weatherforecasts")
.WithTags("WeatherForecasts")
.AllowAnonymous();
group.MapGet("/", GetWeatherForecastListEndpoint.Handle)
.WithSummary("Get weather forecast list");
}
}
public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
public static class GetWeatherForecastListEndpoint
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public static WeatherForecast[] Handle()
{
var random = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
random.Next(-20, 55),
Summaries[random.Next(Summaries.Length)]
)).ToArray();
}
}