-
Notifications
You must be signed in to change notification settings - Fork 0
Enhance PUT /Expense handling for partial updates, add validation and tests #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -78,32 +78,53 @@ public async Task<ActionResult<Expense>> PostExpense(Expense expense) | |
| [HttpPut("{id}")] | ||
| public async Task<IActionResult> PutExpense(int id, Expense expense) | ||
| { | ||
| if (id != expense.Id) | ||
| if (expense.Id != 0 && id != expense.Id) | ||
| { | ||
| _logger.LogWarning("Mismatched expense id for update. Route id: {RouteId}, Expense id: {ExpenseId}", id, expense.Id); | ||
| return BadRequest(); | ||
| } | ||
|
|
||
| _context.Entry(expense).State = EntityState.Modified; | ||
| var existingExpense = await _context.Expenses.FindAsync(id); | ||
| if (existingExpense == null) | ||
| { | ||
| _logger.LogWarning("Expense with id: {ExpenseId} not found during update", id); | ||
| return NotFound(); | ||
| } | ||
|
|
||
| if (expense.Date != default) | ||
| { | ||
| existingExpense.Date = expense.Date; | ||
| } | ||
|
|
||
| if (expense.Description is not null) | ||
| { | ||
| existingExpense.Description = expense.Description; | ||
| } | ||
|
|
||
| existingExpense.Amount = expense.Amount; | ||
| existingExpense.Category = expense.Category; | ||
| existingExpense.Title = expense.Title; | ||
|
Comment on lines
+104
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These fields are currently overwritten regardless of whether they were provided in the request body. This contradicts the PR's objective of supporting partial updates. If a client omits these fields, they will default to if (expense.Amount != 0)
{
existingExpense.Amount = expense.Amount;
}
if (expense.Category is not null)
{
existingExpense.Category = expense.Category;
}
if (expense.Title is not null)
{
existingExpense.Title = expense.Title;
} |
||
|
|
||
| try | ||
| if (string.IsNullOrWhiteSpace(existingExpense.Description)) | ||
| { | ||
| await _context.SaveChangesAsync(); | ||
| _logger.LogInformation("Updated expense with id: {ExpenseId}", id); | ||
| return BadRequest("Description is required"); | ||
| } | ||
| catch (DbUpdateConcurrencyException) | ||
|
|
||
| if (existingExpense.Description == "午餐" && existingExpense.Amount > 400) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| { | ||
| _logger.LogWarning("Lunch expense with amount over 400 is not allowed"); | ||
| return BadRequest("午餐費用不能超過400元"); | ||
| } | ||
|
|
||
| if (existingExpense.Amount < 0) | ||
| { | ||
| if (!ExpenseExists(id)) | ||
| { | ||
| _logger.LogWarning("Expense with id: {ExpenseId} not found during update", id); | ||
| return NotFound(); | ||
| } | ||
| else | ||
| { | ||
| throw; | ||
| } | ||
| _logger.LogWarning("數量不能為負的"); | ||
| return BadRequest("數量不能為負的"); | ||
| } | ||
|
|
||
| await _context.SaveChangesAsync(); | ||
|
linjinhsien marked this conversation as resolved.
|
||
| _logger.LogInformation("Updated expense with id: {ExpenseId}", id); | ||
|
|
||
| return NoContent(); | ||
| } | ||
|
|
||
|
|
@@ -130,4 +151,4 @@ private bool ExpenseExists(int id) | |
| return _context.Expenses.Any(e => e.Id == id); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -100,4 +100,58 @@ public async Task PostExpense_BoundaryExpense_ReturnsCreatedExpense() | |
| Assert.Equal(newExpense.Date, createdExpense.Date); | ||
| Assert.Equal(newExpense.Title, createdExpense.Title); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task PutExpense_PartialUpdateWithoutDate_KeepsOriginalDate() | ||
| { | ||
| // Arrange | ||
| var existingExpense = new Expense | ||
| { | ||
| Category = "食", | ||
| Description = "早餐", | ||
| Amount = 80, | ||
| Date = DateTime.Parse("2026-01-15"), | ||
| Title = "早餐" | ||
| }; | ||
|
|
||
| _context.Expenses.Add(existingExpense); | ||
| await _context.SaveChangesAsync(); | ||
|
|
||
| var updateRequest = new Expense | ||
| { | ||
| Description = "午餐", | ||
| Amount = 120 | ||
| }; | ||
|
|
||
| // Act | ||
| var result = await _controller.PutExpense(existingExpense.Id, updateRequest); | ||
| var updatedExpense = await _context.Expenses.FindAsync(existingExpense.Id); | ||
|
|
||
| // Assert | ||
| Assert.IsType<NoContentResult>(result); | ||
| Assert.NotNull(updatedExpense); | ||
| Assert.Equal(DateTime.Parse("2026-01-15"), updatedExpense!.Date); | ||
| Assert.Equal("午餐", updatedExpense.Description); | ||
| Assert.Equal(120, updatedExpense.Amount); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To properly verify the partial update logic, this test should also assert that fields not included in the Assert.Equal(120, updatedExpense.Amount);
Assert.Equal("食", updatedExpense.Category);
Assert.Equal("早餐", updatedExpense.Title); |
||
| } | ||
|
|
||
| [Fact] | ||
| public async Task PutExpense_MismatchedId_ReturnsBadRequest() | ||
| { | ||
| // Arrange | ||
| var updateRequest = new Expense | ||
| { | ||
| Id = 99, | ||
| Description = "晚餐", | ||
| Amount = 100, | ||
| Date = DateTime.Parse("2026-01-10") | ||
| }; | ||
|
|
||
| // Act | ||
| var result = await _controller.PutExpense(1, updateRequest); | ||
|
|
||
| // Assert | ||
| Assert.IsType<BadRequestResult>(result); | ||
| } | ||
|
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.