1+ using ColumnizerLib ;
2+
3+ using LogExpert . Core . Classes . Log ;
4+ using LogExpert . Core . Entities ;
5+ using LogExpert . Core . Interface ;
6+
7+ using Moq ;
8+
9+ using NUnit . Framework ;
10+
11+ namespace LogExpert . Tests ;
12+
13+ [ TestFixture ]
14+ internal class RolloverFilenameHandlerNullTests
15+ {
16+ /// <summary>
17+ /// Verifies that GetNameList does not throw when GetLogfileInfo returns null
18+ /// for rollover file candidates. This simulates the SFTP scenario where
19+ /// constructing SftpLogFileInfo fails for non-existent files.
20+ /// </summary>
21+ [ Test ]
22+ public void GetNameList_WhenGetLogfileInfoReturnsNull_DoesNotThrow ( )
23+ {
24+ // Arrange: Create a mock ILogFileInfo for the "base" file
25+ var baseFileInfo = new Mock < ILogFileInfo > ( ) ;
26+ _ = baseFileInfo . Setup ( f => f . FileName ) . Returns ( "app.log" ) ;
27+ _ = baseFileInfo . Setup ( f => f . DirectoryName ) . Returns ( "sftp://host/var/log" ) ;
28+ _ = baseFileInfo . Setup ( f => f . DirectorySeparatorChar ) . Returns ( '/' ) ;
29+ _ = baseFileInfo . Setup ( f => f . FileExists ) . Returns ( true ) ;
30+
31+ // Arrange: Create a mock IFileSystemPlugin that returns null for rollover files
32+ var mockFs = new Mock < IFileSystemPlugin > ( ) ;
33+ _ = mockFs . Setup ( fs => fs . CanHandleUri ( It . IsAny < string > ( ) ) ) . Returns ( true ) ;
34+ // Return null for any GetLogfileInfo call — simulates constructor failure
35+ _ = mockFs . Setup ( fs => fs . GetLogfileInfo ( It . IsAny < string > ( ) ) ) . Returns ( ( ILogFileInfo ) null ) ;
36+
37+ // Arrange: Create a mock IPluginRegistry
38+ var mockRegistry = new Mock < IPluginRegistry > ( ) ;
39+ _ = mockRegistry . Setup ( r => r . FindFileSystemForUri ( It . IsAny < string > ( ) ) ) . Returns ( mockFs . Object ) ;
40+
41+ MultiFileOptions options = new ( )
42+ {
43+ FormatPattern = "*$J(.)" ,
44+ MaxDayTry = 5
45+ } ;
46+
47+ RolloverFilenameHandler handler = new ( baseFileInfo . Object , options ) ;
48+
49+ // Act & Assert: Should not throw NullReferenceException
50+ LinkedList < string > result = null ;
51+ Assert . DoesNotThrow ( ( ) => result = handler . GetNameList ( mockRegistry . Object ) ) ;
52+
53+ // The list should contain only the base file
54+ Assert . That ( result , Is . Not . Null ) ;
55+ Assert . That ( result . Count , Is . EqualTo ( 1 ) ) ;
56+ Assert . That ( result . First . Value , Does . Contain ( "app.log" ) ) ;
57+ }
58+
59+ /// <summary>
60+ /// Verifies that GetNameList does not throw when FindFileSystemForUri returns null.
61+ /// This could happen with an unrecognized URI scheme.
62+ /// </summary>
63+ [ Test ]
64+ public void GetNameList_WhenFindFileSystemReturnsNull_DoesNotThrow ( )
65+ {
66+ // Arrange
67+ var baseFileInfo = new Mock < ILogFileInfo > ( ) ;
68+ _ = baseFileInfo . Setup ( f => f . FileName ) . Returns ( "app.log" ) ;
69+ _ = baseFileInfo . Setup ( f => f . DirectoryName ) . Returns ( "custom://host/logs" ) ;
70+ _ = baseFileInfo . Setup ( f => f . DirectorySeparatorChar ) . Returns ( '/' ) ;
71+ _ = baseFileInfo . Setup ( f => f . FileExists ) . Returns ( true ) ;
72+
73+ var mockRegistry = new Mock < IPluginRegistry > ( ) ;
74+ _ = mockRegistry . Setup ( r => r . FindFileSystemForUri ( It . IsAny < string > ( ) ) ) . Returns ( ( IFileSystemPlugin ) null ) ;
75+
76+ MultiFileOptions options = new ( )
77+ {
78+ FormatPattern = "*$J(.)" ,
79+ MaxDayTry = 3
80+ } ;
81+
82+ RolloverFilenameHandler handler = new ( baseFileInfo . Object , options ) ;
83+
84+ // Act & Assert
85+ LinkedList < string > result = null ;
86+ Assert . DoesNotThrow ( ( ) => result = handler . GetNameList ( mockRegistry . Object ) ) ;
87+
88+ Assert . That ( result , Is . Not . Null ) ;
89+ Assert . That ( result . Count , Is . EqualTo ( 1 ) ) ;
90+ }
91+
92+ /// <summary>
93+ /// Verifies that GetNameList correctly finds rollover files when GetLogfileInfo
94+ /// returns a valid ILogFileInfo with FileExists = true. Ensures the null guard
95+ /// does not break the normal happy path.
96+ /// </summary>
97+ [ Test ]
98+ public void GetNameList_WhenRolloverFilesExist_ReturnsAllFiles ( )
99+ {
100+ // Arrange: base file
101+ var baseFileInfo = new Mock < ILogFileInfo > ( ) ;
102+ _ = baseFileInfo . Setup ( f => f . FileName ) . Returns ( "app.log" ) ;
103+ _ = baseFileInfo . Setup ( f => f . DirectoryName ) . Returns ( "/var/log" ) ;
104+ _ = baseFileInfo . Setup ( f => f . DirectorySeparatorChar ) . Returns ( '/' ) ;
105+
106+ // Arrange: rollover file .log.1 exists, .log.2 does not
107+ var rollover1Info = new Mock < ILogFileInfo > ( ) ;
108+ _ = rollover1Info . Setup ( f => f . FileExists ) . Returns ( true ) ;
109+
110+ var mockFs = new Mock < IFileSystemPlugin > ( ) ;
111+ _ = mockFs . Setup ( fs => fs . CanHandleUri ( It . IsAny < string > ( ) ) ) . Returns ( true ) ;
112+
113+ // First call (for .log.1) returns a file that exists
114+ // Second call (for .log.2) returns null (simulating constructor failure)
115+ _ = mockFs . SetupSequence ( fs => fs . GetLogfileInfo ( It . IsAny < string > ( ) ) )
116+ . Returns ( rollover1Info . Object )
117+ . Returns ( ( ILogFileInfo ) null ) ;
118+
119+ var mockRegistry = new Mock < IPluginRegistry > ( ) ;
120+ _ = mockRegistry . Setup ( r => r . FindFileSystemForUri ( It . IsAny < string > ( ) ) ) . Returns ( mockFs . Object ) ;
121+
122+ MultiFileOptions options = new ( )
123+ {
124+ FormatPattern = "*$J(.)" ,
125+ MaxDayTry = 3
126+ } ;
127+
128+ RolloverFilenameHandler handler = new ( baseFileInfo . Object , options ) ;
129+
130+ // Act
131+ var result = handler . GetNameList ( mockRegistry . Object ) ;
132+
133+ // Assert: base file + 1 rollover file
134+ Assert . That ( result . Count , Is . EqualTo ( 2 ) ) ;
135+ }
136+ }
0 commit comments