@@ -6,7 +6,7 @@ use std::path::Path;
66use predicates:: prelude:: * ;
77use serde_json:: Value ;
88
9- use support:: rpass;
9+ use support:: { reencrypting_gpg_script , rpass} ;
1010
1111#[ test]
1212fn init_creates_missing_store_and_writes_gpg_id ( ) {
@@ -196,6 +196,143 @@ fn init_auto_commits_when_store_is_git_repository() {
196196 ) ;
197197}
198198
199+ #[ test]
200+ fn init_re_encrypts_existing_entries_with_new_recipients ( ) {
201+ let store = tempfile:: TempDir :: new ( ) . expect ( "temp dir" ) ;
202+ write_file ( store
. path ( ) . join ( ".gpg-id" ) , "[email protected] \n " ) ; 203+ write_file ( store. path ( ) . join ( "entry.gpg" ) , "secret\n " ) ;
204+ write_file ( store. path ( ) . join ( "subdir/nested.gpg" ) , "nested-secret\n " ) ;
205+
206+ let ( gpg, log_file) = reencrypting_gpg_script ( store. path ( ) ) ;
207+
208+ rpass ( )
209+ . env ( "PASSWORD_STORE_GPG" , & gpg)
210+ . args ( [
211+ "--store-dir" ,
212+ store. path ( ) . to_str ( ) . expect ( "store path" ) ,
213+ "init" ,
214+ 215+ ] )
216+ . assert ( )
217+ . success ( )
218+ . stdout ( "Password store initialized for [email protected] \n " ) 219+ . stderr ( "" ) ;
220+
221+ let log = fs:: read_to_string ( & log_file) . expect ( "log file" ) ;
222+ assert ! (
223+ log
. contains
( "recipient:[email protected] " ) , 224+ "expected new recipient in log, got: {log}"
225+ ) ;
226+ let encrypt_count = log. lines ( ) . filter ( |l| * l == "encrypt" ) . count ( ) ;
227+ assert_eq ! (
228+ encrypt_count, 2 ,
229+ "expected 2 entries re-encrypted, got: {encrypt_count}"
230+ ) ;
231+
232+ assert_eq ! (
233+ fs:: read_to_string( store. path( ) . join( "entry.gpg" ) ) . expect( "entry" ) ,
234+ "secret\n " ,
235+ "content should be preserved after re-encryption"
236+ ) ;
237+ assert_eq ! (
238+ fs:: read_to_string( store. path( ) . join( "subdir/nested.gpg" ) ) . expect( "nested entry" ) ,
239+ "nested-secret\n " ,
240+ "nested content should be preserved after re-encryption"
241+ ) ;
242+ }
243+
244+ #[ test]
245+ fn init_skips_entries_in_subdirectory_with_own_gpg_id ( ) {
246+ let store = tempfile:: TempDir :: new ( ) . expect ( "temp dir" ) ;
247+ write_file ( store
. path ( ) . join ( ".gpg-id" ) , "[email protected] \n " ) ; 248+ write_file ( store
. path ( ) . join ( "team/.gpg-id" ) , "[email protected] \n " ) ; 249+ write_file ( store. path ( ) . join ( "entry.gpg" ) , "root-secret\n " ) ;
250+ write_file ( store. path ( ) . join ( "team/entry.gpg" ) , "team-secret\n " ) ;
251+
252+ let ( gpg, log_file) = reencrypting_gpg_script ( store. path ( ) ) ;
253+
254+ rpass ( )
255+ . env ( "PASSWORD_STORE_GPG" , & gpg)
256+ . args ( [
257+ "--store-dir" ,
258+ store. path ( ) . to_str ( ) . expect ( "store path" ) ,
259+ "init" ,
260+ 261+ ] )
262+ . assert ( )
263+ . success ( ) ;
264+
265+ let log = fs:: read_to_string ( & log_file) . expect ( "log file" ) ;
266+ let encrypt_count = log. lines ( ) . filter ( |l| * l == "encrypt" ) . count ( ) ;
267+ assert_eq ! (
268+ encrypt_count, 1 ,
269+ "only the root entry should be re-encrypted, not the team entry with its own .gpg-id; log: {log}"
270+ ) ;
271+ }
272+
273+ #[ test]
274+ fn init_re_encrypts_only_entries_in_target_subfolder ( ) {
275+ let store = tempfile:: TempDir :: new ( ) . expect ( "temp dir" ) ;
276+ write_file ( store
. path ( ) . join ( ".gpg-id" ) , "[email protected] \n " ) ; 277+ write_file (
278+ store. path ( ) . join ( "team/.gpg-id" ) ,
279+ 280+ ) ;
281+ write_file ( store. path ( ) . join ( "root-entry.gpg" ) , "root-secret\n " ) ;
282+ write_file ( store. path ( ) . join ( "team/entry.gpg" ) , "team-secret\n " ) ;
283+
284+ let ( gpg, log_file) = reencrypting_gpg_script ( store. path ( ) ) ;
285+
286+ rpass ( )
287+ . env ( "PASSWORD_STORE_GPG" , & gpg)
288+ . args ( [
289+ "--store-dir" ,
290+ store. path ( ) . to_str ( ) . expect ( "store path" ) ,
291+ "init" ,
292+ "--path" ,
293+ "team" ,
294+ 295+ ] )
296+ . assert ( )
297+ . success ( ) ;
298+
299+ let log = fs:: read_to_string ( & log_file) . expect ( "log file" ) ;
300+ let encrypt_count = log. lines ( ) . filter ( |l| * l == "encrypt" ) . count ( ) ;
301+ assert_eq ! (
302+ encrypt_count, 1 ,
303+ "only team/entry.gpg should be re-encrypted; log: {log}"
304+ ) ;
305+ assert ! (
306+ log
. contains
( "recipient:[email protected] " ) , 307+ "new team recipient should be used; log: {log}"
308+ ) ;
309+ }
310+
311+ #[ test]
312+ fn init_skips_re_encryption_when_store_has_no_entries ( ) {
313+ let store = tempfile:: TempDir :: new ( ) . expect ( "temp dir" ) ;
314+
315+ let ( gpg, log_file) = reencrypting_gpg_script ( store. path ( ) ) ;
316+
317+ rpass ( )
318+ . env ( "PASSWORD_STORE_GPG" , & gpg)
319+ . args ( [
320+ "--store-dir" ,
321+ store. path ( ) . to_str ( ) . expect ( "store path" ) ,
322+ "init" ,
323+ 324+ ] )
325+ . assert ( )
326+ . success ( )
327+ . stdout ( "Password store initialized for [email protected] \n " ) ; 328+
329+ let log = fs:: read_to_string ( & log_file) . unwrap_or_default ( ) ;
330+ assert ! (
331+ log. is_empty( ) ,
332+ "no re-encryption should happen with empty store; log: {log}"
333+ ) ;
334+ }
335+
199336fn git < const N : usize > ( path : & Path , args : [ & str ; N ] ) {
200337 let status = std:: process:: Command :: new ( "git" )
201338 . arg ( "-C" )
0 commit comments