Compare commits
14 Commits
ca55f34b8a
...
3d0d9faf12
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d0d9faf12 | |||
| 83fc5bdb1d | |||
| 2a68fa8257 | |||
| db51476d4d | |||
| d2c1c61830 | |||
| cfaf05fa1d | |||
| 5074cb5696 | |||
| f12822b7de | |||
| c3567fcd59 | |||
| 226e5e4e3e | |||
| 3a40657b1d | |||
| 90df1edf75 | |||
| dc122ec2d9 | |||
| 1577465024 |
@ -31,6 +31,8 @@ The following functions are supported:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that `geometry` must contain at least two points.
|
||||||
|
|
||||||
- DELETE `/api/streets/<streetname>`: Delete a street
|
- DELETE `/api/streets/<streetname>`: Delete a street
|
||||||
|
|
||||||
Deletes the street with the given name.
|
Deletes the street with the given name.
|
||||||
@ -63,6 +65,6 @@ The following functions are supported:
|
|||||||
"x": int,
|
"x": int,
|
||||||
"y": int
|
"y": int
|
||||||
},
|
},
|
||||||
"method": "Backend" | "Database" | "PostGIS" // Optional, default is "Backend"
|
"usePostGIS": bool // Optional, default is 'false'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
88
src/API/StreetController.cs
Normal file
88
src/API/StreetController.cs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NetTopologySuite.Geometries;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/streets")]
|
||||||
|
public class StreetController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly StreetService StreetService;
|
||||||
|
|
||||||
|
public StreetController(StreetService streetService, ILogger<StreetController> logger)
|
||||||
|
{
|
||||||
|
StreetService = streetService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /api/streets/
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> CreateStreet([FromBody] CreateStreetDTO dto)
|
||||||
|
{
|
||||||
|
if (dto == null)
|
||||||
|
return BadRequest("Invalid request body.");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var geometry = StreetService.ToGeometry(dto.Geometry);
|
||||||
|
|
||||||
|
var street = new Street(dto.Name, dto.Capacity, geometry);
|
||||||
|
var createdStreet = await StreetService.CreateStreetAsync(street);
|
||||||
|
|
||||||
|
return Created();
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
return Conflict(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /api/streets/{streetname}
|
||||||
|
[HttpGet("{streetname}")]
|
||||||
|
public async Task<IActionResult> GetStreet(string streetname)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var street = await StreetService.GetStreetAsync(streetname);
|
||||||
|
return Ok(street);
|
||||||
|
}
|
||||||
|
catch (KeyNotFoundException ex)
|
||||||
|
{
|
||||||
|
return NotFound(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE /api/streets/{streetname}
|
||||||
|
[HttpDelete("{streetname}")]
|
||||||
|
public async Task<IActionResult> DeleteStreet(string streetname)
|
||||||
|
{
|
||||||
|
var deleted = await StreetService.DeleteStreetAsync(streetname);
|
||||||
|
if (!deleted)
|
||||||
|
return NotFound($"Street '{streetname}' not found.");
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PATCH /api/streets/{streetname}
|
||||||
|
[HttpPatch("{streetname}")]
|
||||||
|
public async Task<IActionResult> AddPoint(string streetname, [FromBody] AddPointDTO dto)
|
||||||
|
{
|
||||||
|
if (dto == null)
|
||||||
|
return BadRequest("Invalid request body.");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await StreetService.AddPointAsync(streetname, StreetService.ToGeometryPoint(dto.Point), dto.usePostGIS);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
catch (KeyNotFoundException ex)
|
||||||
|
{
|
||||||
|
return NotFound(ex);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(ex);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return Conflict("Concurrency conflict. Please retry your request.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/Application/AddPointDTO.cs
Normal file
5
src/Application/AddPointDTO.cs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
public class AddPointDTO
|
||||||
|
{
|
||||||
|
public CoordinateDTO Point { get; set; }
|
||||||
|
public bool usePostGIS { get; set; }
|
||||||
|
}
|
||||||
5
src/Application/CoordinateDTO.cs
Normal file
5
src/Application/CoordinateDTO.cs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
public class CoordinateDTO
|
||||||
|
{
|
||||||
|
public int X { get; set; }
|
||||||
|
public int Y { get; set; }
|
||||||
|
}
|
||||||
6
src/Application/CreateStreetDTO.cs
Normal file
6
src/Application/CreateStreetDTO.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
public class CreateStreetDTO
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public int Capacity { get; set; }
|
||||||
|
public List<CoordinateDTO> Geometry { get; set; }
|
||||||
|
}
|
||||||
6
src/Application/GetStreetDTO.cs
Normal file
6
src/Application/GetStreetDTO.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
public class GetStreetDTO
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public int Capacity { get; set; }
|
||||||
|
public List<CoordinateDTO> Geometry { get; set; }
|
||||||
|
}
|
||||||
81
src/Application/StreetService.cs
Normal file
81
src/Application/StreetService.cs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
using System.Data;
|
||||||
|
using NetTopologySuite.Geometries;
|
||||||
|
|
||||||
|
public class StreetService
|
||||||
|
{
|
||||||
|
private readonly StreetRepository repository;
|
||||||
|
|
||||||
|
public StreetService(StreetRepository repository)
|
||||||
|
{
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Point ToGeometryPoint(CoordinateDTO coordinate)
|
||||||
|
{
|
||||||
|
return new Point(coordinate.X, coordinate.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LineString ToGeometry(List<CoordinateDTO> coordinates)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (coordinates == null || coordinates.Count() < 2)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Street must contain at least two points.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var geometryFactory = new GeometryFactory();
|
||||||
|
|
||||||
|
return geometryFactory.CreateLineString(coordinates.Select(g => new Coordinate(g.X, g.Y)).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Street> CreateStreetAsync(Street street)
|
||||||
|
{
|
||||||
|
if (await repository.ExistsAsync(street.Name))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Street '{street.Name}' already exists.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await repository.AddAsync(street);
|
||||||
|
return street;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteStreetAsync(string name)
|
||||||
|
{
|
||||||
|
if (!await repository.ExistsAsync(name))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Street '{name}' does not exist.");
|
||||||
|
}
|
||||||
|
return await repository.RemoveAsync(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GetStreetDTO> GetStreetAsync(string name)
|
||||||
|
{
|
||||||
|
var street = await repository.GetByNameAsync(name);
|
||||||
|
if (street == null)
|
||||||
|
{
|
||||||
|
throw new KeyNotFoundException($"Street '{name}' not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GetStreetDTO { Name = street.Name, Capacity = street.Capacity, Geometry = street.Geometry.Coordinates.Select(c => new CoordinateDTO { X = (int)c.X, Y = (int)c.Y }).ToList() };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddPointAsync(string name, Point point, bool usePostGIS)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (usePostGIS)
|
||||||
|
{
|
||||||
|
await repository.UpdateGeometryWithPostGIS(name, point);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await repository.UpdateGeometryInBackend(name, point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
50
src/Domain/Street.cs
Normal file
50
src/Domain/Street.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using NetTopologySuite.Geometries;
|
||||||
|
|
||||||
|
public class Street
|
||||||
|
{
|
||||||
|
public string Name { get; private set; }
|
||||||
|
public int Capacity { get; private set; }
|
||||||
|
// Could be changed to a more general geometry type instead of the implementation specific LineString
|
||||||
|
public LineString Geometry { get; private set; }
|
||||||
|
|
||||||
|
[Timestamp]
|
||||||
|
public uint Version { get; set; }
|
||||||
|
|
||||||
|
public Street(string name, int capacity, LineString geometry)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Street name cannot be empty.", nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (capacity <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be positive.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (geometry == null || geometry.IsEmpty)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Geometry cannot be null or empty.", nameof(geometry));
|
||||||
|
}
|
||||||
|
|
||||||
|
Name = name;
|
||||||
|
Capacity = capacity;
|
||||||
|
Geometry = geometry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddPointToGeometry(Coordinate point, bool atEnd = true)
|
||||||
|
{
|
||||||
|
if (point == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(point), "Point cannot be null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var coords = Geometry.Coordinates.ToList();
|
||||||
|
|
||||||
|
coords.Add(point);
|
||||||
|
|
||||||
|
Geometry = new LineString(coords.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,23 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
|
|
||||||
public class StreetDbContext : DbContext
|
public class StreetDbContext : DbContext
|
||||||
{
|
{
|
||||||
|
public DbSet<Street> Streets { get; set; }
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder.HasPostgresExtension("postgis");
|
||||||
|
|
||||||
|
modelBuilder.Entity<Street>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(s => s.Name);
|
||||||
|
|
||||||
|
entity.Property(s => s.Capacity).IsRequired();
|
||||||
|
|
||||||
|
entity.Property(s => s.Geometry).HasColumnType("geometry(LineString,4326)").IsRequired();
|
||||||
|
|
||||||
|
entity.Property(s => s.Version).IsRowVersion();
|
||||||
|
});
|
||||||
|
}
|
||||||
public StreetDbContext(DbContextOptions<StreetDbContext> options) : base(options)
|
public StreetDbContext(DbContextOptions<StreetDbContext> options) : base(options)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
86
src/Infrastructure/StreetRepository.cs
Normal file
86
src/Infrastructure/StreetRepository.cs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NetTopologySuite.Geometries;
|
||||||
|
|
||||||
|
public class StreetRepository
|
||||||
|
{
|
||||||
|
private readonly StreetDbContext Context;
|
||||||
|
|
||||||
|
public StreetRepository(StreetDbContext context)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Street> GetByNameAsync(string name)
|
||||||
|
{
|
||||||
|
return await Context.Streets.FirstOrDefaultAsync(s => s.Name == name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ExistsAsync(string name)
|
||||||
|
{
|
||||||
|
return await Context.Streets.AnyAsync(s => s.Name == name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddAsync(Street street)
|
||||||
|
{
|
||||||
|
await Context.Streets.AddAsync(street);
|
||||||
|
await Context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> RemoveAsync(string name)
|
||||||
|
{
|
||||||
|
var street = await GetByNameAsync(name);
|
||||||
|
if (street == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Context.Streets.Remove(street);
|
||||||
|
await Context.SaveChangesAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateGeometryInBackend(string name, Geometry point)
|
||||||
|
{
|
||||||
|
var street = await GetByNameAsync(name);
|
||||||
|
|
||||||
|
if (street == null)
|
||||||
|
{
|
||||||
|
throw new KeyNotFoundException($"Street '{name}' not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
street.AddPointToGeometry(point.Coordinate);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
catch (DbUpdateConcurrencyException)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Concurrency conflict: The street was modified by another transaction.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateGeometryWithPostGIS(string name, Geometry point)
|
||||||
|
{
|
||||||
|
var street = await GetByNameAsync(name);
|
||||||
|
|
||||||
|
if (street == null)
|
||||||
|
{
|
||||||
|
throw new KeyNotFoundException($"Street '{name}' not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// PostgreSQL's UPDATE already checks for concurrency by default
|
||||||
|
await Context.Database.ExecuteSqlRawAsync(
|
||||||
|
"UPDATE \"Streets\" SET \"Geometry\" = ST_AddPoint(\"Geometry\", ST_SetSRID(ST_MakePoint({0}, {1}), 4326)) WHERE \"Name\" = {2}",
|
||||||
|
point.Coordinate.X, point.Coordinate.Y, name
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (DbUpdateConcurrencyException)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Concurrency conflict: The street was modified by another transaction.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,6 +9,9 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.3" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.3" />
|
||||||
|
<PackageReference Include="NetTopologySuite" Version="2.6.0" />
|
||||||
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
|
<PackageReference Include="npgsql.entityframeworkcore.postgresql.nettopologysuite" Version="9.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -1,7 +1,18 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Services.AddDbContext<StreetDbContext>(options => options.UseNpgsql("TBD", o => o.UseNetTopologySuite()));
|
||||||
|
|
||||||
|
builder.Services.AddScoped<StreetRepository>();
|
||||||
|
builder.Services.AddScoped<StreetService>();
|
||||||
|
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.MapGet("/", () => "Hello World!");
|
app.UseRouting();
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using FluentAssertions.Json;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
public class APITests : IClassFixture<StreetWebApplicationFactory<Program>>
|
public class APITests : IClassFixture<StreetWebApplicationFactory<Program>>
|
||||||
{
|
{
|
||||||
@ -26,9 +29,11 @@ public class APITests : IClassFixture<StreetWebApplicationFactory<Program>>
|
|||||||
{
|
{
|
||||||
name = "Kaiserstraße",
|
name = "Kaiserstraße",
|
||||||
capacity = 100,
|
capacity = 100,
|
||||||
geometry = new[] { new { x = 10, y = 20 } }
|
geometry = new[] { new { x = 10, y = 20 }, new { x = 15, y = 25 } }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Console.WriteLine(JsonConvert.SerializeObject(street));
|
||||||
|
|
||||||
var post_response = await client.PostAsJsonAsync("/api/streets/", street);
|
var post_response = await client.PostAsJsonAsync("/api/streets/", street);
|
||||||
|
|
||||||
post_response.StatusCode.Should().Be(System.Net.HttpStatusCode.Created);
|
post_response.StatusCode.Should().Be(System.Net.HttpStatusCode.Created);
|
||||||
@ -37,9 +42,11 @@ public class APITests : IClassFixture<StreetWebApplicationFactory<Program>>
|
|||||||
|
|
||||||
get_response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK);
|
get_response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK);
|
||||||
|
|
||||||
var responseContent = await get_response.Content.ReadFromJsonAsync<object>();
|
var expected_street = JObject.Parse(JsonConvert.SerializeObject(street));
|
||||||
|
|
||||||
street.Should().BeEquivalentTo(responseContent);
|
var response_street = JObject.Parse(await get_response.Content.ReadAsStringAsync());
|
||||||
|
|
||||||
|
response_street.Should().BeEquivalentTo(expected_street);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -49,7 +56,7 @@ public class APITests : IClassFixture<StreetWebApplicationFactory<Program>>
|
|||||||
{
|
{
|
||||||
name = "Adenauerring",
|
name = "Adenauerring",
|
||||||
capacity = 100,
|
capacity = 100,
|
||||||
geometry = new[] { new { x = 10, y = 20 } }
|
geometry = new[] { new { x = 10, y = 20 }, new { x = 50, y = 70 } }
|
||||||
};
|
};
|
||||||
|
|
||||||
await client.PostAsJsonAsync("/api/streets/", street);
|
await client.PostAsJsonAsync("/api/streets/", street);
|
||||||
@ -58,7 +65,7 @@ public class APITests : IClassFixture<StreetWebApplicationFactory<Program>>
|
|||||||
{
|
{
|
||||||
name = "Adenauerring",
|
name = "Adenauerring",
|
||||||
capacity = 200,
|
capacity = 200,
|
||||||
geometry = new[] { new { x = 30, y = 40 } }
|
geometry = new[] { new { x = 30, y = 40 }, new { x = 100, y = 150 } }
|
||||||
};
|
};
|
||||||
|
|
||||||
var response = await client.PostAsJsonAsync("/api/streets", duplicate_street);
|
var response = await client.PostAsJsonAsync("/api/streets", duplicate_street);
|
||||||
@ -71,7 +78,7 @@ public class APITests : IClassFixture<StreetWebApplicationFactory<Program>>
|
|||||||
{
|
{
|
||||||
var response = await client.GetAsync("/api/streets/Englerstraße");
|
var response = await client.GetAsync("/api/streets/Englerstraße");
|
||||||
|
|
||||||
response.StatusCode.Should().Be(System.Net.HttpStatusCode.NoContent);
|
response.StatusCode.Should().Be(System.Net.HttpStatusCode.NotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -81,7 +88,7 @@ public class APITests : IClassFixture<StreetWebApplicationFactory<Program>>
|
|||||||
{
|
{
|
||||||
name = "Moltkestraße",
|
name = "Moltkestraße",
|
||||||
capacity = 100,
|
capacity = 100,
|
||||||
geometry = new[] { new { x = 10, y = 20 } }
|
geometry = new[] { new { x = 10, y = 20 }, new { x = 50, y = 100 } }
|
||||||
};
|
};
|
||||||
|
|
||||||
await client.PostAsJsonAsync("/api/streets/", street);
|
await client.PostAsJsonAsync("/api/streets/", street);
|
||||||
@ -115,7 +122,7 @@ public class APITests : IClassFixture<StreetWebApplicationFactory<Program>>
|
|||||||
{
|
{
|
||||||
name = "Ettlingerstraße",
|
name = "Ettlingerstraße",
|
||||||
capacity = 50,
|
capacity = 50,
|
||||||
geometry = new[] { new { x = 5, y = 3 } }
|
geometry = new[] { new { x = 5, y = 3 }, new { x = 6, y = 4 } }
|
||||||
};
|
};
|
||||||
|
|
||||||
await client.PostAsJsonAsync("/api/streets/", street);
|
await client.PostAsJsonAsync("/api/streets/", street);
|
||||||
@ -133,22 +140,25 @@ public class APITests : IClassFixture<StreetWebApplicationFactory<Program>>
|
|||||||
|
|
||||||
get_response_no_method.StatusCode.Should().Be(System.Net.HttpStatusCode.OK);
|
get_response_no_method.StatusCode.Should().Be(System.Net.HttpStatusCode.OK);
|
||||||
|
|
||||||
var content_no_method = get_response_no_method.Content.ReadFromJsonAsync<object>();
|
var expected_street_no_method = new
|
||||||
|
|
||||||
var expected_content_no_method = new
|
|
||||||
{
|
{
|
||||||
name = "Ettlingerstraße",
|
name = "Ettlingerstraße",
|
||||||
capacity = 50,
|
capacity = 50,
|
||||||
geometry = new[] { new { x = 5, y = 3 }, new { x = 15, y = 30 } }
|
geometry = new[] { new { x = 5, y = 3 }, new { x = 6, y = 4 }, new { x = 15, y = 30 } }
|
||||||
};
|
};
|
||||||
|
|
||||||
expected_content_no_method.Should().BeEquivalentTo(content_no_method);
|
var expected_street_json = JObject.Parse(JsonConvert.SerializeObject(expected_street_no_method));
|
||||||
|
|
||||||
|
var response_street_no_method = JObject.Parse(await get_response_no_method.Content.ReadAsStringAsync());
|
||||||
|
|
||||||
|
response_street_no_method.Should().BeEquivalentTo(expected_street_json);
|
||||||
|
|
||||||
|
|
||||||
// Test with "Backend" method
|
// Test with "Backend" method
|
||||||
var update_backend = new
|
var update_backend = new
|
||||||
{
|
{
|
||||||
Point = new { x = 20, y = 35 },
|
Point = new { x = 20, y = 35 },
|
||||||
Method = "Backend"
|
usePostGIS = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
var patch_response_backend = await client.PatchAsJsonAsync("/api/streets/Ettlingerstraße", update_backend);
|
var patch_response_backend = await client.PatchAsJsonAsync("/api/streets/Ettlingerstraße", update_backend);
|
||||||
@ -159,43 +169,35 @@ public class APITests : IClassFixture<StreetWebApplicationFactory<Program>>
|
|||||||
var update_postgis = new
|
var update_postgis = new
|
||||||
{
|
{
|
||||||
Point = new { x = 25, y = 40 },
|
Point = new { x = 25, y = 40 },
|
||||||
Method = "PostGIS"
|
usePostGIS = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
var patch_response_postgis = await client.PatchAsJsonAsync("/api/streets/Ettlingerstraße", update_postgis);
|
var patch_response_postgis = await client.PatchAsJsonAsync("/api/streets/Ettlingerstraße", update_postgis);
|
||||||
|
|
||||||
patch_response_postgis.StatusCode.Should().Be(System.Net.HttpStatusCode.OK);
|
patch_response_postgis.StatusCode.Should().Be(System.Net.HttpStatusCode.OK);
|
||||||
|
|
||||||
// Test with "Database" method
|
|
||||||
var update_database = new
|
|
||||||
{
|
|
||||||
Point = new { x = 30, y = 45 },
|
|
||||||
Method = "Database"
|
|
||||||
};
|
|
||||||
|
|
||||||
var patch_response_database = await client.PatchAsJsonAsync("/api/streets/Ettlingerstraße", update_database);
|
|
||||||
|
|
||||||
patch_response_database.StatusCode.Should().Be(System.Net.HttpStatusCode.OK);
|
|
||||||
|
|
||||||
var get_response_final = await client.GetAsync("/api/streets/Ettlingerstraße");
|
var get_response_final = await client.GetAsync("/api/streets/Ettlingerstraße");
|
||||||
|
|
||||||
get_response_final.StatusCode.Should().Be(System.Net.HttpStatusCode.OK);
|
get_response_final.StatusCode.Should().Be(System.Net.HttpStatusCode.OK);
|
||||||
|
|
||||||
var content_final = await get_response_final.Content.ReadFromJsonAsync<object>();
|
var expected_street_final = new
|
||||||
var expected_content_final = new
|
|
||||||
{
|
{
|
||||||
name = "Ettlingerstraße",
|
name = "Ettlingerstraße",
|
||||||
capacity = 50,
|
capacity = 50,
|
||||||
geometry = new[]
|
geometry = new[]
|
||||||
{
|
{
|
||||||
new { x = 5, y = 3 },
|
new { x = 5, y = 3 },
|
||||||
|
new { x = 6, y = 4 },
|
||||||
new { x = 15, y = 30 },
|
new { x = 15, y = 30 },
|
||||||
new { x = 20, y = 35 },
|
new { x = 20, y = 35 },
|
||||||
new { x = 25, y = 40 },
|
new { x = 25, y = 40 },
|
||||||
new { x = 30, y = 45 }
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
expected_content_final.Should().BeEquivalentTo(content_final);
|
var expected_street = JObject.Parse(JsonConvert.SerializeObject(expected_street_final));
|
||||||
|
|
||||||
|
var response_street = JObject.Parse(await get_response_final.Content.ReadAsStringAsync());
|
||||||
|
|
||||||
|
response_street.Should().BeEquivalentTo(expected_street);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -19,7 +19,7 @@ public class StreetWebApplicationFactory<Program> : WebApplicationFactory<Progra
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Use the test database
|
// Use the test database
|
||||||
services.AddDbContext<StreetDbContext>(options => options.UseNpgsql(dbTestContainer.GetConnectionString()));
|
services.AddDbContext<StreetDbContext>(options => options.UseNpgsql(dbTestContainer.GetConnectionString(), o => o.UseNetTopologySuite()));
|
||||||
|
|
||||||
// Ensure the database is created before running the tests
|
// Ensure the database is created before running the tests
|
||||||
using var scope = services.BuildServiceProvider().CreateScope();
|
using var scope = services.BuildServiceProvider().CreateScope();
|
||||||
|
|||||||
@ -10,11 +10,15 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
||||||
<PackageReference Include="FluentAssertions" Version="8.2.0" />
|
<PackageReference Include="FluentAssertions" Version="8.2.0" />
|
||||||
|
<PackageReference Include="FluentAssertions.Json" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.3" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.3" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
|
<PackageReference Include="NetTopologySuite" Version="2.6.0" />
|
||||||
<PackageReference Include="npgsql" Version="9.0.3" />
|
<PackageReference Include="npgsql" Version="9.0.3" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
|
<PackageReference Include="npgsql.entityframeworkcore.postgresql.nettopologysuite" Version="9.0.4" />
|
||||||
|
<PackageReference Include="npgsql.Nettopologysuite" Version="9.0.3" />
|
||||||
<PackageReference Include="Testcontainers" Version="4.3.0" />
|
<PackageReference Include="Testcontainers" Version="4.3.0" />
|
||||||
<PackageReference Include="Testcontainers.PostgreSql" Version="4.3.0" />
|
<PackageReference Include="Testcontainers.PostgreSql" Version="4.3.0" />
|
||||||
<PackageReference Include="Testcontainers.Xunit" Version="4.3.0" />
|
<PackageReference Include="Testcontainers.Xunit" Version="4.3.0" />
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user