@@ -48,6 +48,92 @@ async fn test_settings(_: PgPoolOptions, options: PgConnectOptions) {
4848 assert ! ( new_settings. wireguard_enabled) ;
4949}
5050
51+ #[ sqlx:: test]
52+ async fn test_patch_settings_clears_optional_fields ( _: PgPoolOptions , options : PgConnectOptions ) {
53+ let pool = setup_pool ( options) . await ;
54+ let ( client, _client_state) = make_test_client ( pool. clone ( ) ) . await ;
55+
56+ let auth = Auth :: new ( "admin" , "pass123" ) ;
57+ let response = client. post ( "/api/v1/auth" ) . json ( & auth) . send ( ) . await ;
58+ assert_eq ! ( response. status( ) , StatusCode :: OK ) ;
59+
60+ // --- smtp_user & smtp_password ---
61+
62+ // set smtp_user and smtp_password (include the required trio so validation passes)
63+ let patch: SettingsPatch = serde_json:: from_str (
64+ r#"{
65+ "smtp_server": "smtp.example.com",
66+ "smtp_port": 587,
67+ "smtp_sender": "noreply@example.com",
68+ "smtp_user": "testuser",
69+ "smtp_password": "testpass"
70+ }"# ,
71+ )
72+ . unwrap ( ) ;
73+ let response = client. patch ( "/api/v1/settings" ) . json ( & patch) . send ( ) . await ;
74+ assert_eq ! ( response. status( ) , StatusCode :: OK ) ;
75+
76+ // verify fields are set
77+ let response = client. get ( "/api/v1/settings" ) . send ( ) . await ;
78+ assert_eq ! ( response. status( ) , StatusCode :: OK ) ;
79+ let settings: Settings = response. json ( ) . await ;
80+ assert_eq ! (
81+ settings. smtp_user,
82+ Some ( "testuser" . to_string( ) ) ,
83+ "smtp_user should be set after initial PATCH"
84+ ) ;
85+ // smtp_password is redacted in the API response; verify via DB
86+ let from_db = Settings :: get ( & pool) . await . unwrap ( ) . unwrap ( ) ;
87+ assert ! (
88+ from_db. smtp_password. is_some( ) ,
89+ "smtp_password should be set in DB after initial PATCH"
90+ ) ;
91+
92+ // clear smtp_user and smtp_password by sending null
93+ let patch: SettingsPatch =
94+ serde_json:: from_str ( r#"{ "smtp_user": null, "smtp_password": null }"# ) . unwrap ( ) ;
95+ let response = client. patch ( "/api/v1/settings" ) . json ( & patch) . send ( ) . await ;
96+ assert_eq ! ( response. status( ) , StatusCode :: OK ) ;
97+
98+ // assert both fields are cleared in the DB
99+ let from_db = Settings :: get ( & pool) . await . unwrap ( ) . unwrap ( ) ;
100+ assert ! (
101+ from_db. smtp_user. is_none( ) ,
102+ "smtp_user should be cleared to None after PATCH with null"
103+ ) ;
104+ assert ! (
105+ from_db. smtp_password. is_none( ) ,
106+ "smtp_password should be cleared to None after PATCH with null"
107+ ) ;
108+
109+ // --- ldap_user_rdn_attr ---
110+
111+ // set ldap_user_rdn_attr
112+ let patch: SettingsPatch = serde_json:: from_str ( r#"{ "ldap_user_rdn_attr": "uid" }"# ) . unwrap ( ) ;
113+ let response = client. patch ( "/api/v1/settings" ) . json ( & patch) . send ( ) . await ;
114+ assert_eq ! ( response. status( ) , StatusCode :: OK ) ;
115+
116+ // verify field is set
117+ let from_db = Settings :: get ( & pool) . await . unwrap ( ) . unwrap ( ) ;
118+ assert_eq ! (
119+ from_db. ldap_user_rdn_attr,
120+ Some ( "uid" . to_string( ) ) ,
121+ "ldap_user_rdn_attr should be set after PATCH"
122+ ) ;
123+
124+ // clear ldap_user_rdn_attr by sending null
125+ let patch: SettingsPatch = serde_json:: from_str ( r#"{ "ldap_user_rdn_attr": null }"# ) . unwrap ( ) ;
126+ let response = client. patch ( "/api/v1/settings" ) . json ( & patch) . send ( ) . await ;
127+ assert_eq ! ( response. status( ) , StatusCode :: OK ) ;
128+
129+ // assert field is cleared in the DB
130+ let from_db = Settings :: get ( & pool) . await . unwrap ( ) . unwrap ( ) ;
131+ assert ! (
132+ from_db. ldap_user_rdn_attr. is_none( ) ,
133+ "ldap_user_rdn_attr should be cleared to None after PATCH with null"
134+ ) ;
135+ }
136+
51137// JSON fragment containing all required LDAP fields except ldap_url (add that at the call site).
52138const VALID_LDAP_FIELDS_NO_URL : & str = r#"
53139 "ldap_bind_username": "cn=admin,dc=example,dc=com",
0 commit comments