diff --git a/ExpenseAPI/Controllers/ExpenseController.cs b/ExpenseAPI/Controllers/ExpenseController.cs index f165592..27c42d3 100644 --- a/ExpenseAPI/Controllers/ExpenseController.cs +++ b/ExpenseAPI/Controllers/ExpenseController.cs @@ -78,32 +78,53 @@ public async Task> PostExpense(Expense expense) [HttpPut("{id}")] public async Task 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; - 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) + { + _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(); + _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); } } -} \ No newline at end of file +} diff --git a/ExpenseApiTest/ExpenseApiTest.cs b/ExpenseApiTest/ExpenseApiTest.cs index 49ffebf..f61f6d0 100644 --- a/ExpenseApiTest/ExpenseApiTest.cs +++ b/ExpenseApiTest/ExpenseApiTest.cs @@ -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(result); + Assert.NotNull(updatedExpense); + Assert.Equal(DateTime.Parse("2026-01-15"), updatedExpense!.Date); + Assert.Equal("午餐", updatedExpense.Description); + Assert.Equal(120, updatedExpense.Amount); + } + + [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(result); + } + } \ No newline at end of file