@@ -150,6 +150,42 @@ def test_update_file_payload_roundtrip(self):
150150 self .assertIsInstance (got , (bytes , bytearray ))
151151 self .assertEqual (got , raw )
152152
153+ def test_otp_only_update_creates_new_revision (self ):
154+ s : Secret = new_secret (self .owner , name = "otp-add" )
155+ rev_before = s .current_revision
156+ self .assertFalse (rev_before .otp_key_set )
157+
158+ changes_before = SecretChange .objects .filter (secret = s ).count ()
159+
160+ # OTP-only update: omit password, keep existing via merge logic.
161+ RevisionService .save_payload (
162+ secret = s ,
163+ actor = self .owner ,
164+ payload = {
165+ "otp_key" : "JBSWY3DPEHPK3PXP" ,
166+ "digits" : "6" ,
167+ "algorithm" : "SHA1" ,
168+ },
169+ )
170+
171+ s .refresh_from_db ()
172+ rev_after = s .current_revision
173+ self .assertNotEqual (rev_after .id , rev_before .id )
174+ self .assertTrue (rev_after .otp_key_set )
175+ self .assertEqual (rev_after .get_data (self .owner )["otp_key" ], "JBSWY3DPEHPK3PXP" )
176+
177+ # Re-saving identical OTP-only payload should be a no-op.
178+ RevisionService .save_payload (
179+ secret = s ,
180+ actor = self .owner ,
181+ payload = {
182+ "otp_key" : "JBSWY3DPEHPK3PXP" ,
183+ "digits" : "6" ,
184+ "algorithm" : "SHA1" ,
185+ },
186+ )
187+ self .assertEqual (SecretChange .objects .filter (secret = s ).count (), changes_before + 1 )
188+
153189 def test_delete_marks_secret_and_denies_visibility_and_read (self ):
154190 s : Secret = new_secret (self .owner , name = "todelete" , access_policy = AccessPolicy .ANY )
155191 s .status = SecretStatus .DELETED
0 commit comments