From 9882a2290ba50edbbd81791ade55231689d66693 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 16 Jul 2026 15:04:23 +0000
Subject: [PATCH 1/2] Initial plan
From 10bef2d14c10b194c370c344f078328b91b7b670 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 16 Jul 2026 15:13:28 +0000
Subject: [PATCH 2/2] fix: address validation only fires when address fields
are updated
- Remove [Required] from Address, City, Country, PostalCode fields in UserPersonalUpdateModel
- Implement IValidatableObject with conditional validation: if any address field is provided, all required address fields (Address, City, Country, PostalCode) must be filled
- Add HasAnyAddressField() helper method
- Fix OnPostUpdatePersonalInfoAsync to only call UpdateShippingAddress when address fields are present
- Add validation summary div to Admin/User.cshtml personal info form
- Add unit tests for HasAnyAddressField and conditional address validation
Closes #1422
---
src/DevBetterWeb.Web/Pages/Admin/User.cshtml | 1 +
.../Pages/Admin/User.cshtml.cs | 5 +-
.../Pages/User/UserPersonalUpdateModel.cs | 33 ++++--
.../UserPersonalUpdateModelValidateTests.cs | 100 ++++++++++++++++++
4 files changed, 132 insertions(+), 7 deletions(-)
create mode 100644 tests/DevBetterWeb.UnitTests/Web/Models/UserPersonalUpdateModelValidateTests.cs
diff --git a/src/DevBetterWeb.Web/Pages/Admin/User.cshtml b/src/DevBetterWeb.Web/Pages/Admin/User.cshtml
index 012b135ca..17c02bce9 100644
--- a/src/DevBetterWeb.Web/Pages/Admin/User.cshtml
+++ b/src/DevBetterWeb.Web/Pages/Admin/User.cshtml
@@ -128,6 +128,7 @@
+
diff --git a/src/DevBetterWeb.Web/Pages/Admin/User.cshtml.cs b/src/DevBetterWeb.Web/Pages/Admin/User.cshtml.cs
index bf9e48aac..3272d5a6b 100644
--- a/src/DevBetterWeb.Web/Pages/Admin/User.cshtml.cs
+++ b/src/DevBetterWeb.Web/Pages/Admin/User.cshtml.cs
@@ -272,7 +272,10 @@ public async Task OnPostUpdatePersonalInfoAsync(string userId)
member.UpdatePEInfo(UserPersonalUpdateModel.PEFriendCode, UserPersonalUpdateModel.PEUsername, false);
member.UpdateAboutInfo(UserPersonalUpdateModel.AboutInfo, false);
member.UpdateAddress(UserPersonalUpdateModel.Address, false);
- member.UpdateShippingAddress(UserPersonalUpdateModel.Address!, UserPersonalUpdateModel.City!, UserPersonalUpdateModel.State!, UserPersonalUpdateModel.PostalCode!, UserPersonalUpdateModel.Country!, false);
+ if (UserPersonalUpdateModel.HasAnyAddressField())
+ {
+ member.UpdateShippingAddress(UserPersonalUpdateModel.Address!, UserPersonalUpdateModel.City!, UserPersonalUpdateModel.State!, UserPersonalUpdateModel.PostalCode!, UserPersonalUpdateModel.Country!, false);
+ }
member.UpdateDiscord(UserPersonalUpdateModel.DiscordUsername, false);
member.UpdateEmail(UserPersonalUpdateModel.Email, false);
diff --git a/src/DevBetterWeb.Web/Pages/User/UserPersonalUpdateModel.cs b/src/DevBetterWeb.Web/Pages/User/UserPersonalUpdateModel.cs
index 516e68509..98104c567 100644
--- a/src/DevBetterWeb.Web/Pages/User/UserPersonalUpdateModel.cs
+++ b/src/DevBetterWeb.Web/Pages/User/UserPersonalUpdateModel.cs
@@ -1,24 +1,21 @@
-using System.ComponentModel.DataAnnotations;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Diagnostics.Metrics;
using DevBetterWeb.Core.Entities;
namespace DevBetterWeb.Web.Pages.User;
-public class UserPersonalUpdateModel
+public class UserPersonalUpdateModel : IValidatableObject
{
[Required]
public string? FirstName { get; set; }
[Required]
public string? LastName { get; set; }
- [Required]
public string? Address { get; set; }
- [Required]
public string? City { get; set; }
public string? State { get; set; }
- [Required]
public string? Country { get; set; }
- [Required]
public string? PostalCode { get; set; }
[Range(1, 31)]
[BirthdayDay]
@@ -60,4 +57,28 @@ public UserPersonalUpdateModel(Member member)
PEUsername = member.PEUsername;
DiscordUsername = member.DiscordUsername;
}
+
+ public bool HasAnyAddressField() =>
+ !string.IsNullOrWhiteSpace(Address) ||
+ !string.IsNullOrWhiteSpace(City) ||
+ !string.IsNullOrWhiteSpace(Country) ||
+ !string.IsNullOrWhiteSpace(PostalCode) ||
+ !string.IsNullOrWhiteSpace(State);
+
+ public IEnumerable Validate(ValidationContext validationContext)
+ {
+ if (!HasAnyAddressField()) yield break;
+
+ if (string.IsNullOrWhiteSpace(Address))
+ yield return new ValidationResult("The Address field is required when providing address information.", new[] { nameof(Address) });
+
+ if (string.IsNullOrWhiteSpace(City))
+ yield return new ValidationResult("The City field is required when providing address information.", new[] { nameof(City) });
+
+ if (string.IsNullOrWhiteSpace(Country))
+ yield return new ValidationResult("The Country field is required when providing address information.", new[] { nameof(Country) });
+
+ if (string.IsNullOrWhiteSpace(PostalCode))
+ yield return new ValidationResult("The Postal Code field is required when providing address information.", new[] { nameof(PostalCode) });
+ }
}
diff --git a/tests/DevBetterWeb.UnitTests/Web/Models/UserPersonalUpdateModelValidateTests.cs b/tests/DevBetterWeb.UnitTests/Web/Models/UserPersonalUpdateModelValidateTests.cs
new file mode 100644
index 000000000..45359064b
--- /dev/null
+++ b/tests/DevBetterWeb.UnitTests/Web/Models/UserPersonalUpdateModelValidateTests.cs
@@ -0,0 +1,100 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using DevBetterWeb.Web.Pages.User;
+using Xunit;
+
+namespace DevBetterWeb.UnitTests.Web.Models;
+
+public class UserPersonalUpdateModelValidateTests
+{
+ private static List Validate(UserPersonalUpdateModel model)
+ {
+ var results = new List();
+ var context = new ValidationContext(model);
+ Validator.TryValidateObject(model, context, results, validateAllProperties: true);
+ return results;
+ }
+
+ [Fact]
+ public void NoAddressFields_NoValidationErrors()
+ {
+ var model = new UserPersonalUpdateModel
+ {
+ FirstName = "Jane",
+ LastName = "Doe",
+ Email = "jane@example.com"
+ };
+
+ var errors = Validate(model);
+
+ Assert.Empty(errors);
+ }
+
+ [Fact]
+ public void AllAddressFieldsProvided_NoValidationErrors()
+ {
+ var model = new UserPersonalUpdateModel
+ {
+ FirstName = "Jane",
+ LastName = "Doe",
+ Address = "123 Main St",
+ City = "Springfield",
+ Country = "US",
+ PostalCode = "12345",
+ State = "IL"
+ };
+
+ var errors = Validate(model);
+
+ Assert.Empty(errors);
+ }
+
+ [Fact]
+ public void OnlyCityProvided_ValidationErrorsForOtherAddressFields()
+ {
+ var model = new UserPersonalUpdateModel
+ {
+ FirstName = "Jane",
+ LastName = "Doe",
+ City = "Springfield"
+ };
+
+ var errors = Validate(model);
+
+ Assert.Contains(errors, e => e.MemberNames != null && System.Linq.Enumerable.Contains(e.MemberNames, nameof(UserPersonalUpdateModel.Address)));
+ Assert.Contains(errors, e => e.MemberNames != null && System.Linq.Enumerable.Contains(e.MemberNames, nameof(UserPersonalUpdateModel.Country)));
+ Assert.Contains(errors, e => e.MemberNames != null && System.Linq.Enumerable.Contains(e.MemberNames, nameof(UserPersonalUpdateModel.PostalCode)));
+ }
+
+ [Fact]
+ public void HasAnyAddressField_ReturnsFalseWhenAllEmpty()
+ {
+ var model = new UserPersonalUpdateModel();
+
+ Assert.False(model.HasAnyAddressField());
+ }
+
+ [Fact]
+ public void HasAnyAddressField_ReturnsTrueWhenAddressSet()
+ {
+ var model = new UserPersonalUpdateModel { Address = "123 Main St" };
+
+ Assert.True(model.HasAnyAddressField());
+ }
+
+ [Fact]
+ public void HasAnyAddressField_ReturnsTrueWhenOnlyCitySet()
+ {
+ var model = new UserPersonalUpdateModel { City = "Springfield" };
+
+ Assert.True(model.HasAnyAddressField());
+ }
+
+ [Fact]
+ public void HasAnyAddressField_ReturnsFalseForWhitespaceOnly()
+ {
+ var model = new UserPersonalUpdateModel { Address = " ", City = " " };
+
+ Assert.False(model.HasAnyAddressField());
+ }
+}