@@ -21,28 +21,28 @@ public async Task Cleanup()
2121 }
2222
2323 [ Test ]
24- public async Task EntireFileWatcher_Create_ReturnsInitialContent ( )
24+ public async Task EntireFile_Create_ReturnsInitialContent ( )
2525 {
2626 var testFile = new FileInfo ( Path . Combine ( _testDirectory . FullName , "test.txt" ) ) ;
2727 await File . WriteAllTextAsync ( testFile . FullName , "Initial content" ) ;
2828
29- var ( watcher , initialContent ) = EntireFileWatcher . Create ( testFile ) ;
29+ var ( watcher , initialContent ) = FileWatcher . Create ( testFile , FileWatchMode . EntireFile ) ;
3030
3131 initialContent . Should ( ) . Be ( "Initial content" ) ;
3232 watcher . Should ( ) . NotBeNull ( ) ;
3333 }
3434
3535 [ Test ]
36- public async Task EntireFileWatcher_OnChange_EmitsEntireFileContent ( )
36+ public async Task EntireFile_OnChange_EmitsEntireFileContent ( )
3737 {
3838 var testFile = new FileInfo ( Path . Combine ( _testDirectory . FullName , "test.txt" ) ) ;
3939 await File . WriteAllTextAsync ( testFile . FullName , "Initial content" ) ;
4040
41- var ( watcher , _) = EntireFileWatcher . Create ( testFile ) ;
41+ var ( watcher , _) = FileWatcher . Create ( testFile , FileWatchMode . EntireFile ) ;
4242 var changedContent = "" ;
4343 var tcs = new TaskCompletionSource < bool > ( ) ;
4444
45- watcher . OnChange ( content =>
45+ watcher . OnContentChanged ( content =>
4646 {
4747 changedContent = content ;
4848 tcs . TrySetResult ( true ) ;
@@ -57,29 +57,29 @@ public async Task EntireFileWatcher_OnChange_EmitsEntireFileContent()
5757 }
5858
5959 [ Test ]
60- public async Task LineByLineFileWatcher_Create_ReturnsInitialLines ( )
60+ public async Task LineByLine_Create_ReturnsInitialContent ( )
6161 {
6262 var testFile = new FileInfo ( Path . Combine ( _testDirectory . FullName , "test.txt" ) ) ;
6363 await File . WriteAllLinesAsync ( testFile . FullName , [ "Line 1" , "Line 2" , "Line 3" ] ) ;
6464
65- var ( watcher , initialContent ) = LineByLineFileWatcher . Create ( testFile ) ;
65+ var ( watcher , initialContent ) = FileWatcher . Create ( testFile , FileWatchMode . LineByLine ) ;
6666
67- initialContent . Should ( ) . BeEquivalentTo ( [ "Line 1" , "Line 2" , "Line 3" ] ) ;
67+ initialContent . Should ( ) . Contain ( "Line" ) ;
6868 watcher . Should ( ) . NotBeNull ( ) ;
6969 }
7070
7171 [ Test ]
72- public async Task LineByLineFileWatcher_OnChange_EmitsOnlyNewLines ( )
72+ public async Task LineByLine_OnChange_EmitsOnlyNewLines ( )
7373 {
7474 var testFile = new FileInfo ( Path . Combine ( _testDirectory . FullName , "test.txt" ) ) ;
7575 await File . WriteAllLinesAsync ( testFile . FullName , [ "Line 1" , "Line 2" ] ) ;
7676
77- var ( watcher , _) = LineByLineFileWatcher . Create ( testFile ) ;
77+ var ( watcher , _) = FileWatcher . Create ( testFile , FileWatchMode . LineByLine ) ;
7878 var newLines = new List < string > ( ) ;
7979 var tcs = new TaskCompletionSource < bool > ( ) ;
8080 var expectedNewLines = 2 ;
8181
82- watcher . OnChange ( line =>
82+ watcher . OnContentChanged ( line =>
8383 {
8484 newLines . Add ( line ) ;
8585 if ( newLines . Count >= expectedNewLines )
@@ -89,23 +89,24 @@ public async Task LineByLineFileWatcher_OnChange_EmitsOnlyNewLines()
8989 await Task . Delay ( 100 ) ; // Give watcher time to start
9090 await File . WriteAllLinesAsync ( testFile . FullName , [ "Line 1" , "Line 2" , "Line 3" , "Line 4" ] ) ;
9191
92+ // Wait for change event (with timeout)
9293 await Task . WhenAny ( tcs . Task , Task . Delay ( 5000 ) ) ;
9394
9495 newLines . Should ( ) . BeEquivalentTo ( [ "Line 3" , "Line 4" ] ) ;
9596 }
9697
9798 [ Test ]
98- public async Task LineByLineFileWatcher_OnChange_HandlesMultipleChanges ( )
99+ public async Task LineByLine_OnChange_HandlesMultipleChanges ( )
99100 {
100101 var testFile = new FileInfo ( Path . Combine ( _testDirectory . FullName , "test.txt" ) ) ;
101102 await File . WriteAllLinesAsync ( testFile . FullName , [ "Line 1" ] ) ;
102103
103- var ( watcher , _) = LineByLineFileWatcher . Create ( testFile ) ;
104+ var ( watcher , _) = FileWatcher . Create ( testFile , FileWatchMode . LineByLine ) ;
104105 var allNewLines = new List < string > ( ) ;
105106 var tcs = new TaskCompletionSource < bool > ( ) ;
106107 var expectedTotalLines = 3 ; // Line 2, Line 3, Line 4
107108
108- watcher . OnChange ( line =>
109+ watcher . OnContentChanged ( line =>
109110 {
110111 allNewLines . Add ( line ) ;
111112 if ( allNewLines . Count >= expectedTotalLines )
@@ -118,21 +119,22 @@ public async Task LineByLineFileWatcher_OnChange_HandlesMultipleChanges()
118119 await Task . Delay ( 200 ) ;
119120 await File . WriteAllLinesAsync ( testFile . FullName , [ "Line 1" , "Line 2" , "Line 3" , "Line 4" ] ) ;
120121
122+ // Wait for change event (with timeout)
121123 await Task . WhenAny ( tcs . Task , Task . Delay ( 5000 ) ) ;
122124
123125 allNewLines . Should ( ) . BeEquivalentTo ( [ "Line 2" , "Line 3" , "Line 4" ] ) ;
124126 }
125127
126128 [ Test ]
127- public async Task LineByLineFileWatcher_OnChange_DoesNotEmitWhenNoNewLines ( )
129+ public async Task LineByLine_OnChange_DoesNotEmitWhenNoNewLines ( )
128130 {
129131 var testFile = new FileInfo ( Path . Combine ( _testDirectory . FullName , "test.txt" ) ) ;
130132 await File . WriteAllLinesAsync ( testFile . FullName , [ "Line 1" , "Line 2" ] ) ;
131133
132- var ( watcher , _) = LineByLineFileWatcher . Create ( testFile ) ;
134+ var ( watcher , _) = FileWatcher . Create ( testFile , FileWatchMode . LineByLine ) ;
133135 var newLines = new List < string > ( ) ;
134136
135- watcher . OnChange ( line =>
137+ watcher . OnContentChanged ( line =>
136138 {
137139 newLines . Add ( line ) ;
138140 } ) ;
@@ -145,4 +147,153 @@ public async Task LineByLineFileWatcher_OnChange_DoesNotEmitWhenNoNewLines()
145147
146148 newLines . Should ( ) . BeEmpty ( ) ;
147149 }
150+
151+ [ Test ]
152+ public async Task LineByLine_CreateWithDirectory_ReturnsInitialContentFromLatestFile ( )
153+ {
154+ var file1 = new FileInfo ( Path . Combine ( _testDirectory . FullName , "Journal.2025-11-22T100000.01.log" ) ) ;
155+ var file2 = new FileInfo ( Path . Combine ( _testDirectory . FullName , "Journal.2025-11-22T110000.01.log" ) ) ;
156+
157+ await File . WriteAllLinesAsync ( file1 . FullName , [ "Old line 1" , "Old line 2" ] ) ;
158+ await Task . Delay ( 10 ) ; // Ensure file2 has a later timestamp
159+ await File . WriteAllLinesAsync ( file2 . FullName , [ "New line 1" , "New line 2" ] ) ;
160+
161+ var ( watcher , initialContent ) = FileWatcher . Create ( _testDirectory , "Journal.*.log" , FileWatchMode . LineByLine ) ;
162+
163+ initialContent . Should ( ) . Contain ( "New line" ) ;
164+ watcher . Should ( ) . NotBeNull ( ) ;
165+ watcher . CurrentFile . Name . Should ( ) . Be ( "Journal.2025-11-22T110000.01.log" ) ;
166+ }
167+
168+ [ Test ]
169+ public async Task LineByLine_CreateWithDirectory_SwitchesToNewFile ( )
170+ {
171+ var file1 = new FileInfo ( Path . Combine ( _testDirectory . FullName , "Journal.2025-11-22T100000.01.log" ) ) ;
172+ await File . WriteAllLinesAsync ( file1 . FullName , [ "Line 1" ] ) ;
173+
174+ var ( watcher , _) = FileWatcher . Create ( _testDirectory , "Journal.*.log" , FileWatchMode . LineByLine ) ;
175+ var allLines = new List < string > ( ) ;
176+ var tcs = new TaskCompletionSource < bool > ( ) ;
177+
178+ watcher . OnContentChanged ( line =>
179+ {
180+ allLines . Add ( line ) ;
181+ if ( allLines . Count >= 3 ) // Expecting 3 total lines
182+ tcs . TrySetResult ( true ) ;
183+ } ) ;
184+
185+ await Task . Delay ( 100 ) ; // Give watcher time to start
186+
187+ // Add line to first file
188+ await File . WriteAllLinesAsync ( file1 . FullName , [ "Line 1" , "Line 2" ] ) ;
189+ await Task . Delay ( 200 ) ;
190+
191+ // Create a new journal file
192+ var file2 = new FileInfo ( Path . Combine ( _testDirectory . FullName , "Journal.2025-11-22T110000.01.log" ) ) ;
193+ await File . WriteAllLinesAsync ( file2 . FullName , [ "New file line 1" ] ) ;
194+ await Task . Delay ( 300 ) ; // Give watcher time to detect and switch
195+
196+ // Add another line to the new file
197+ await File . WriteAllLinesAsync ( file2 . FullName , [ "New file line 1" , "New file line 2" ] ) ;
198+
199+ // Wait for all expected lines
200+ await Task . WhenAny ( tcs . Task , Task . Delay ( 5000 ) ) ;
201+
202+ allLines . Should ( ) . Contain ( "Line 2" ) ;
203+ allLines . Should ( ) . Contain ( "New file line 2" ) ;
204+ }
205+
206+ [ Test ]
207+ public async Task LineByLine_CreateWithDirectory_IgnoresIrrelevantFiles ( )
208+ {
209+ var journalFile = new FileInfo ( Path . Combine ( _testDirectory . FullName , "Journal.2025-11-22T100000.01.log" ) ) ;
210+ await File . WriteAllLinesAsync ( journalFile . FullName , [ "Journal line 1" ] ) ;
211+
212+ var ( watcher , _) = FileWatcher . Create ( _testDirectory , "Journal.*.log" , FileWatchMode . LineByLine ) ;
213+ var allLines = new List < string > ( ) ;
214+
215+ watcher . OnContentChanged ( line =>
216+ {
217+ allLines . Add ( line ) ;
218+ } ) ;
219+
220+ await Task . Delay ( 100 ) ; // Give watcher time to start
221+
222+ // Create and write to an irrelevant file (doesn't match pattern)
223+ var irrelevantFile = new FileInfo ( Path . Combine ( _testDirectory . FullName , "Status.json" ) ) ;
224+ await File . WriteAllTextAsync ( irrelevantFile . FullName , "{ \" status\" : \" test\" }" ) ;
225+ await Task . Delay ( 300 ) ;
226+
227+ // Write to another irrelevant file
228+ var anotherIrrelevant = new FileInfo ( Path . Combine ( _testDirectory . FullName , "SomeOtherFile.txt" ) ) ;
229+ await File . WriteAllTextAsync ( anotherIrrelevant . FullName , "Irrelevant content" ) ;
230+ await Task . Delay ( 300 ) ;
231+
232+ // Write to the journal file
233+ await File . WriteAllLinesAsync ( journalFile . FullName , [ "Journal line 1" , "Journal line 2" ] ) ;
234+ await Task . Delay ( 300 ) ;
235+
236+ watcher . CurrentFile . Name . Should ( ) . Be ( "Journal.2025-11-22T100000.01.log" ) ;
237+ allLines . Should ( ) . ContainSingle ( ) ;
238+ allLines . Should ( ) . Contain ( "Journal line 2" ) ;
239+ }
240+
241+ [ Test ]
242+ public async Task LineByLine_OnFileChanged_InvokesCallbackWhenSwitchingFiles ( )
243+ {
244+ var file1 = new FileInfo ( Path . Combine ( _testDirectory . FullName , "Journal.2025-11-22T100000.01.log" ) ) ;
245+ await File . WriteAllLinesAsync ( file1 . FullName , [ "Line 1" ] ) ;
246+
247+ var ( watcher , _) = FileWatcher . Create ( _testDirectory , "Journal.*.log" , FileWatchMode . LineByLine ) ;
248+ var fileChanges = new List < string > ( ) ;
249+ var tcs = new TaskCompletionSource < bool > ( ) ;
250+
251+ watcher . OnFileChanged ( newFile =>
252+ {
253+ fileChanges . Add ( newFile . Name ) ;
254+ tcs . TrySetResult ( true ) ;
255+ } ) ;
256+
257+ watcher . OnContentChanged ( line => { /* Do nothing, just need to start watching */ } ) ;
258+
259+ await Task . Delay ( 100 ) ; // Give watcher time to start
260+
261+ // Create and write to a new journal file
262+ var file2 = new FileInfo ( Path . Combine ( _testDirectory . FullName , "Journal.2025-11-22T110000.01.log" ) ) ;
263+ await File . WriteAllLinesAsync ( file2 . FullName , [ "New file line 1" ] ) ;
264+ await Task . Delay ( 300 ) ; // Give watcher time to detect and switch
265+
266+ // Wait for file change callback
267+ await Task . WhenAny ( tcs . Task , Task . Delay ( 5000 ) ) ;
268+
269+ fileChanges . Should ( ) . ContainSingle ( ) ;
270+ fileChanges [ 0 ] . Should ( ) . Be ( "Journal.2025-11-22T110000.01.log" ) ;
271+ watcher . CurrentFile . Name . Should ( ) . Be ( "Journal.2025-11-22T110000.01.log" ) ;
272+ }
273+
274+ [ Test ]
275+ public async Task LineByLine_OnFileChanged_NotInvokedWhenNoFileSwitching ( )
276+ {
277+ var testFile = new FileInfo ( Path . Combine ( _testDirectory . FullName , "Journal.2025-11-22T100000.01.log" ) ) ;
278+ await File . WriteAllLinesAsync ( testFile . FullName , [ "Line 1" ] ) ;
279+
280+ var ( watcher , _) = FileWatcher . Create ( _testDirectory , "Journal.*.log" , FileWatchMode . LineByLine ) ;
281+ var fileChanges = new List < string > ( ) ;
282+
283+ watcher . OnFileChanged ( newFile =>
284+ {
285+ fileChanges . Add ( newFile . Name ) ;
286+ } ) ;
287+
288+ watcher . OnContentChanged ( line => { /* Do nothing */ } ) ;
289+
290+ await Task . Delay ( 100 ) ; // Give watcher time to start
291+
292+ // Write to the same file (no file switching)
293+ await File . WriteAllLinesAsync ( testFile . FullName , [ "Line 1" , "Line 2" , "Line 3" ] ) ;
294+ await Task . Delay ( 300 ) ;
295+
296+ fileChanges . Should ( ) . BeEmpty ( ) ;
297+ watcher . CurrentFile . Name . Should ( ) . Be ( "Journal.2025-11-22T100000.01.log" ) ;
298+ }
148299}
0 commit comments