@@ -165,3 +165,85 @@ func TestOrganizeAudiobookMovesNestedTreeRecursively(t *testing.T) {
165165 t .Fatalf ("expected source tree removed, stat err=%v" , err )
166166 }
167167}
168+
169+ func TestOrganizeAudiobookSkipsSymlinks (t * testing.T ) {
170+ root := t .TempDir ()
171+ src := filepath .Join (root , "incoming" , "book" )
172+ outside := filepath .Join (root , "outside.txt" )
173+
174+ if err := os .MkdirAll (src , 0755 ); err != nil {
175+ t .Fatal (err )
176+ }
177+ if err := os .WriteFile (filepath .Join (src , "track01.m4b" ), []byte ("audio" ), 0644 ); err != nil {
178+ t .Fatal (err )
179+ }
180+ if err := os .WriteFile (outside , []byte ("outside" ), 0644 ); err != nil {
181+ t .Fatal (err )
182+ }
183+ if err := os .Symlink (outside , filepath .Join (src , "linked.m4b" )); err != nil {
184+ t .Skipf ("symlinks unavailable: %v" , err )
185+ }
186+
187+ cfg := & config.Config {
188+ FileOrgEnabled : true ,
189+ AudiobookDir : filepath .Join (root , "audiobooks" ),
190+ }
191+ o := NewOrganizer (cfg )
192+
193+ dest , err := o .OrganizeAudiobook (src , "Symlink Book" , "Careful Author" )
194+ if err != nil {
195+ t .Fatalf ("organize failed: %v" , err )
196+ }
197+
198+ if _ , err := os .Stat (filepath .Join (dest , "track01.m4b" )); err != nil {
199+ t .Fatalf ("expected regular track at destination: %v" , err )
200+ }
201+ if _ , err := os .Lstat (filepath .Join (dest , "linked.m4b" )); ! os .IsNotExist (err ) {
202+ t .Fatalf ("expected symlink skipped, lstat err=%v" , err )
203+ }
204+ data , err := os .ReadFile (outside )
205+ if err != nil {
206+ t .Fatalf ("expected symlink target left untouched: %v" , err )
207+ }
208+ if string (data ) != "outside" {
209+ t .Fatalf ("outside file = %q, want %q" , data , "outside" )
210+ }
211+ }
212+
213+ func TestOrganizeAudiobookRejectsSymlinkSource (t * testing.T ) {
214+ root := t .TempDir ()
215+ realSrc := filepath .Join (root , "incoming" , "book" )
216+ linkSrc := filepath .Join (root , "incoming" , "linked-book" )
217+
218+ if err := os .MkdirAll (realSrc , 0755 ); err != nil {
219+ t .Fatal (err )
220+ }
221+ if err := os .WriteFile (filepath .Join (realSrc , "track01.m4b" ), []byte ("audio" ), 0644 ); err != nil {
222+ t .Fatal (err )
223+ }
224+ if err := os .Symlink (realSrc , linkSrc ); err != nil {
225+ t .Skipf ("symlinks unavailable: %v" , err )
226+ }
227+
228+ cfg := & config.Config {
229+ FileOrgEnabled : true ,
230+ AudiobookDir : filepath .Join (root , "audiobooks" ),
231+ }
232+ o := NewOrganizer (cfg )
233+
234+ _ , err := o .OrganizeAudiobook (linkSrc , "Linked Book" , "Careful Author" )
235+ if err == nil {
236+ t .Fatal ("expected symlink source error" )
237+ }
238+
239+ destDir := filepath .Join (cfg .AudiobookDir , "Careful Author" , "Linked Book" )
240+ if _ , err := os .Stat (destDir ); ! os .IsNotExist (err ) {
241+ t .Fatalf ("expected no destination for symlink source, stat err=%v" , err )
242+ }
243+ if _ , err := os .Stat (filepath .Join (realSrc , "track01.m4b" )); err != nil {
244+ t .Fatalf ("expected symlink target left untouched: %v" , err )
245+ }
246+ if _ , err := os .Lstat (linkSrc ); err != nil {
247+ t .Fatalf ("expected symlink source left untouched: %v" , err )
248+ }
249+ }
0 commit comments