ProcessMapPlanDrilling.http - fix encoding.

Handle foreignKeys exceptions as validation exceptions.
Add integr.tests for bad request cases.
This commit is contained in:
ngfrolov 2024-01-22 11:49:45 +05:00
parent 8a38777db3
commit 54ef71411a
Signed by: ng.frolov
GPG Key ID: E99907A0357B29A7
3 changed files with 127 additions and 69 deletions

View File

@ -5,8 +5,10 @@ using AsbCloudApp.Requests;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudDb.Model.ProcessMapPlan; using AsbCloudDb.Model.ProcessMapPlan;
using AsbCloudDb.Model.WellSections;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Npgsql;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -47,8 +49,8 @@ public class ProcessMapPlanBaseRepository<TDto, TEntity> : IProcessMapPlanBaseRe
entity.Obsolete = null; entity.Obsolete = null;
dbSet.Add(entity); dbSet.Add(entity);
} }
result += await context.SaveChangesAsync(token); result += await SaveChangesWithExceptionHandling(token);
} }
return result; return result;
} }
@ -60,28 +62,21 @@ public class ProcessMapPlanBaseRepository<TDto, TEntity> : IProcessMapPlanBaseRe
using var transaction = context.Database.BeginTransaction(); using var transaction = context.Database.BeginTransaction();
var result = 0; var result = 0;
try
var dbSet = context.Set<TEntity>();
var entitiesToMarkDeleted = dbSet
.Where(e => e.IdWell == idWell)
.Where(e => e.Obsolete == null);
var obsolete = DateTimeOffset.UtcNow;
foreach (var entity in entitiesToMarkDeleted)
{ {
var dbSet = context.Set<TEntity>(); entity.IdState = ChangeLogAbstract.IdClearedOnImport;
var entitiesToMarkDeleted = dbSet entity.Obsolete = obsolete;
.Where(e => e.IdWell == idWell) entity.IdEditor = idUser;
.Where(e => e.Obsolete == null);
var obsolete = DateTimeOffset.UtcNow;
foreach (var entity in entitiesToMarkDeleted)
{
entity.IdState = ChangeLogAbstract.IdClearedOnImport;
entity.Obsolete = obsolete;
entity.IdEditor = idUser;
}
result += await context.SaveChangesAsync(token);
result += await InsertRange(idUser, dtos, token);
await transaction.CommitAsync(token);
}
catch
{
await transaction.RollbackAsync(CancellationToken.None);
throw;
} }
result += await SaveChangesWithExceptionHandling(token);
result += await InsertRange(idUser, dtos, token);
await transaction.CommitAsync(token);
return result; return result;
} }
@ -99,7 +94,7 @@ public class ProcessMapPlanBaseRepository<TDto, TEntity> : IProcessMapPlanBaseRe
entity.Obsolete = obsolete; entity.Obsolete = obsolete;
entity.IdEditor = idUser; entity.IdEditor = idUser;
} }
var result = await context.SaveChangesAsync(token); var result = await SaveChangesWithExceptionHandling(token);
return result; return result;
} }
@ -201,48 +196,39 @@ public class ProcessMapPlanBaseRepository<TDto, TEntity> : IProcessMapPlanBaseRe
using var transaction = context.Database.BeginTransaction(); using var transaction = context.Database.BeginTransaction();
var result = 0; var result = 0;
try
var ids = dtos.Select(d => d.Id);
var dbSet = context.Set<TEntity>();
var entitiesToDelete = dbSet
.Where(e => ids.Contains(e.Id));
var updateTime = DateTimeOffset.UtcNow;
foreach (var entity in entitiesToDelete)
{ {
var ids = dtos.Select(d => d.Id); if(entity.Obsolete is not null)
var dbSet = context.Set<TEntity>(); throw new ArgumentInvalidException(nameof(dtos), "Недопустимо редактировать устаревшие записи");
entity.IdState = ChangeLogAbstract.IdStateReplaced;
var entitiesToDelete = dbSet entity.Obsolete = updateTime;
.Where(e => ids.Contains(e.Id)); entity.IdEditor = idUser;
var updateTime = DateTimeOffset.UtcNow;
foreach (var entity in entitiesToDelete)
{
if(entity.Obsolete is not null)
throw new ArgumentInvalidException(nameof(dtos), "Недопустимо редактировать устаревшие записи");
entity.IdState = ChangeLogAbstract.IdStateReplaced;
entity.Obsolete = updateTime;
entity.IdEditor = idUser;
}
result += await context.SaveChangesAsync(token);
var entitiesNew = dtos.Select(Convert);
foreach (var entity in entitiesNew)
{
entity.IdPrevious = entity.Id;
entity.Id = default;
entity.Creation = updateTime;
entity.IdAuthor = idUser;
entity.Obsolete = null;
entity.IdEditor = null;
entity.IdState = ChangeLogAbstract.IdStateActual;
dbSet.Add(entity);
}
result += await context.SaveChangesAsync(token);
await transaction.CommitAsync(token);
} }
catch result += await context.SaveChangesAsync(token);
var entitiesNew = dtos.Select(Convert);
foreach (var entity in entitiesNew)
{ {
await transaction.RollbackAsync(CancellationToken.None); entity.IdPrevious = entity.Id;
throw; entity.Id = default;
entity.Creation = updateTime;
entity.IdAuthor = idUser;
entity.Obsolete = null;
entity.IdEditor = null;
entity.IdState = ChangeLogAbstract.IdStateActual;
dbSet.Add(entity);
} }
result += await SaveChangesWithExceptionHandling(token);
await transaction.CommitAsync(token);
return result; return result;
} }
@ -267,4 +253,25 @@ public class ProcessMapPlanBaseRepository<TDto, TEntity> : IProcessMapPlanBaseRe
return dto; return dto;
} }
private async Task<int> SaveChangesWithExceptionHandling(CancellationToken token)
{
try
{
var result = await context.SaveChangesAsync(token);
return result;
}
catch (DbUpdateException ex)
{
if (ex.InnerException is PostgresException pgException)
TryConvertPostgresExceptionToValidateException(pgException);
throw;
}
}
private static void TryConvertPostgresExceptionToValidateException(PostgresException pgException)
{
if (pgException.SqlState == PostgresErrorCodes.ForeignKeyViolation)
throw new ArgumentInvalidException("dtos", pgException.Message + "\r\n" + pgException.Detail);
}
} }

View File

@ -117,6 +117,57 @@ public class ProcessMapPlanDrillingControllerTest: BaseIntegrationTest
MatchHelper.Match(expected, actual, excludeProps); MatchHelper.Match(expected, actual, excludeProps);
} }
[Fact]
public async Task InsertRange_returns_BadRequest_for_IdWellSectionType()
{
//arrange
var dbset = dbContext.Set<ProcessMapPlanDrilling>();
dbset.RemoveRange(dbset);
dbContext.SaveChanges();
var badDto = dto.Adapt<ProcessMapPlanDrillingDto>();
badDto.IdWellSectionType = int.MaxValue;
//act
var response = await client.InsertRange(dto.IdWell, new[] { badDto });
//assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task InsertRange_returns_BadRequest_for_IdMode()
{
//arrange
var dbset = dbContext.Set<ProcessMapPlanDrilling>();
dbset.RemoveRange(dbset);
dbContext.SaveChanges();
var badDto = dto.Adapt<ProcessMapPlanDrillingDto>();
badDto.IdMode = int.MaxValue;
//act
var response = await client.InsertRange(dto.IdWell, new[] { badDto });
//assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task InsertRange_returns_BadRequest_for_IdWell()
{
//arrange
var dbset = dbContext.Set<ProcessMapPlanDrilling>();
dbset.RemoveRange(dbset);
dbContext.SaveChanges();
var badDto = dto.Adapt<ProcessMapPlanDrillingDto>();
badDto.IdWell = int.MaxValue;
//act
var response = await client.InsertRange(dto.IdWell, new[] { badDto });
//assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact] [Fact]
public async Task ClearAndInsertRange_returns_success() public async Task ClearAndInsertRange_returns_success()
{ {

View File

@ -7,7 +7,7 @@
@id = 1 @id = 1
@idWell = 1 @idWell = 1
### получение данных drill test с панели и сохранение их в ЕЦП ### получение данных drill test с панели и сохранение их в ЕЦП
POST {{baseUrl}}/api/well/{{idWell}}/ProcessMapPlanDrilling POST {{baseUrl}}/api/well/{{idWell}}/ProcessMapPlanDrilling
Content-Type: {{contentType}} Content-Type: {{contentType}}
accept: */* accept: */*
@ -54,12 +54,12 @@ Authorization: {{auth}}
} }
] ]
### Получение всех ### Получение всех
GET {{baseUrl}}/api/well/{{idWell}}/ProcessMapPlanDrilling GET {{baseUrl}}/api/well/{{idWell}}/ProcessMapPlanDrilling
accept: */* accept: */*
Authorization: {{auth}} Authorization: {{auth}}
### замена старых записей новыми ### замена старых записей новыми
POST {{baseUrl}}/api/well/{{idWell}}/ProcessMapPlanDrilling/replace POST {{baseUrl}}/api/well/{{idWell}}/ProcessMapPlanDrilling/replace
Content-Type: {{contentType}} Content-Type: {{contentType}}
accept: */* accept: */*
@ -75,7 +75,7 @@ Authorization: {{auth}}
"idState": 0, "idState": 0,
"idPrevious": 0, "idPrevious": 0,
"idWell": 1, "idWell": 1,
"idWellSectionType": 1, "idWellSectionType": 1500,
"depthStart": 0, "depthStart": 0,
"depthEnd": 10, "depthEnd": 10,
"idMode": 2, "idMode": 2,
@ -106,22 +106,22 @@ Authorization: {{auth}}
} }
] ]
### Получение только актуальных ### Получение только актуальных
GET {{baseUrl}}/api/well/{{idWell}}/ProcessMapPlanDrilling?Moment=3000-01-01 GET {{baseUrl}}/api/well/{{idWell}}/ProcessMapPlanDrilling?Moment=3000-01-01
accept: */* accept: */*
Authorization: {{auth}} Authorization: {{auth}}
### Получение ### Получение
GET {{baseUrl}}/api/well/{{idWell}}/ProcessMapPlanDrilling/dates GET {{baseUrl}}/api/well/{{idWell}}/ProcessMapPlanDrilling/dates
accept: */* accept: */*
Authorization: {{auth}} Authorization: {{auth}}
### Получение изменений за дату ### Получение изменений за дату
GET {{baseUrl}}/api/well/{{idWell}}/ProcessMapPlanDrilling/changeLog?date=2024-01-19 GET {{baseUrl}}/api/well/{{idWell}}/ProcessMapPlanDrilling/changeLog?date=2024-01-19
accept: */* accept: */*
Authorization: {{auth}} Authorization: {{auth}}
### удаление ### удаление
DELETE {{baseUrl}}/api/well/{{idWell}}/ProcessMapPlanDrilling DELETE {{baseUrl}}/api/well/{{idWell}}/ProcessMapPlanDrilling
Content-Type: {{contentType}} Content-Type: {{contentType}}
accept: */* accept: */*