@@ -67,9 +67,17 @@ func TestParseDurhamThread(t *testing.T) {
6767 if firstPost .URL == "" {
6868 t .Error ("First post should have a URL" )
6969 }
70+ if firstPost .Timestamp == "" {
71+ t .Error ("First post should have a timestamp" )
72+ } else {
73+ // Validate it can be parsed
74+ if _ , err := time .Parse (time .RFC3339 , firstPost .Timestamp ); err != nil {
75+ t .Errorf ("Failed to parse timestamp %q: %v" , firstPost .Timestamp , err )
76+ }
77+ }
7078
71- t .Logf ("First post ID: %s, Author: %s, Content length: %d bytes" ,
72- firstPost .ID , firstPost .Author , len (firstPost .Content ))
79+ t .Logf ("First post ID: %s, Author: %s, Timestamp: %s, Content length: %d bytes" ,
80+ firstPost .ID , firstPost .Author , firstPost . Timestamp , len (firstPost .Content ))
7381
7482 // Validate URL format
7583 expectedURLPrefix := threadURL + "#post-"
@@ -78,8 +86,8 @@ func TestParseDurhamThread(t *testing.T) {
7886 }
7987}
8088
81- // TestParseDurhamThreadLastPage validates we can parse a specific page number.
82- func TestParseDurhamThreadLastPage (t * testing.T ) {
89+ // TestParseDurhamThreadPage293 validates we can parse a specific page number.
90+ func TestParseDurhamThreadPage293 (t * testing.T ) {
8391 if testing .Short () {
8492 t .Skip ("skipping integration test in short mode" )
8593 }
@@ -108,4 +116,151 @@ func TestParseDurhamThreadLastPage(t *testing.T) {
108116 t .Fatal ("No posts found on page 293" )
109117 }
110118 t .Logf ("Found %d posts on page 293" , len (page .Posts ))
119+
120+ // Validate last post has timestamp (most important for latest post tracking)
121+ lastPost := page .Posts [len (page .Posts )- 1 ]
122+ if lastPost .Timestamp == "" {
123+ t .Error ("Last post on page 293 should have a timestamp" )
124+ } else {
125+ // Validate it can be parsed
126+ parsedTime , err := time .Parse (time .RFC3339 , lastPost .Timestamp )
127+ if err != nil {
128+ t .Errorf ("Failed to parse timestamp %q: %v" , lastPost .Timestamp , err )
129+ } else {
130+ t .Logf ("Last post ID: %s, Author: %s, Timestamp: %s" ,
131+ lastPost .ID , lastPost .Author , parsedTime .Format (time .RFC3339 ))
132+ }
133+ }
134+ }
135+
136+ // TestParseDurhamThreadLatestPost validates we can fetch the absolute latest post.
137+ func TestParseDurhamThreadLatestPost (t * testing.T ) {
138+ if testing .Short () {
139+ t .Skip ("skipping integration test in short mode" )
140+ }
141+
142+ client := & http.Client {Timeout : 30 * time .Second }
143+ logger := slog .New (slog .NewTextHandler (os .Stdout , & slog.HandlerOptions {Level : slog .LevelInfo }))
144+ s := New (client , logger )
145+
146+ ctx := context .Background ()
147+ threadURL := "https://advrider.com/f/threads/durham-rtp-wednesday-advlunch.365943/"
148+
149+ // Use LatestPost to fetch the most recent post (same as subscription creation does)
150+ post , title , err := s .LatestPost (ctx , threadURL )
151+ if err != nil {
152+ t .Fatalf ("Failed to fetch Durham thread latest post: %v" , err )
153+ }
154+
155+ t .Logf ("Thread title: %q" , title )
156+ if title == "" {
157+ t .Error ("Thread title should not be empty" )
158+ }
159+
160+ // Validate post ID
161+ if post .ID == "" {
162+ t .Fatal ("Latest post should have an ID" )
163+ }
164+ t .Logf ("Latest post ID: %s" , post .ID )
165+
166+ // Validate post timestamp - CRITICAL
167+ if post .Timestamp == "" {
168+ t .Fatal ("Latest post should have a timestamp" )
169+ }
170+ t .Logf ("Latest post timestamp: %s" , post .Timestamp )
171+
172+ // Validate timestamp can be parsed as RFC3339
173+ parsedTime , err := time .Parse (time .RFC3339 , post .Timestamp )
174+ if err != nil {
175+ t .Fatalf ("Failed to parse timestamp %q as RFC3339: %v" , post .Timestamp , err )
176+ }
177+ t .Logf ("Parsed timestamp: %s" , parsedTime .Format (time .RFC3339 ))
178+
179+ // Validate timestamp is reasonable (thread started in 2008, so posts should be after that)
180+ now := time .Now ()
181+ if parsedTime .IsZero () {
182+ t .Error ("Parsed timestamp should not be zero" )
183+ }
184+ if parsedTime .After (now ) {
185+ t .Errorf ("Parsed timestamp %s is in the future (now: %s)" , parsedTime , now )
186+ }
187+ if parsedTime .Year () < 2008 {
188+ t .Errorf ("Parsed timestamp %s is before thread creation (2008)" , parsedTime )
189+ }
190+
191+ // Validate other post fields
192+ if post .Author == "" {
193+ t .Error ("Latest post should have an author" )
194+ }
195+ if post .Content == "" {
196+ t .Error ("Latest post should have content" )
197+ }
198+ t .Logf ("Latest post by %s, content length: %d bytes" , post .Author , len (post .Content ))
199+ }
200+
201+ // TestParseElectricMotorcycleThread validates timestamp parsing for the Electric Motorcycle thread.
202+ func TestParseElectricMotorcycleThread (t * testing.T ) {
203+ if testing .Short () {
204+ t .Skip ("skipping integration test in short mode" )
205+ }
206+
207+ client := & http.Client {Timeout : 30 * time .Second }
208+ logger := slog .New (slog .NewTextHandler (os .Stdout , & slog.HandlerOptions {Level : slog .LevelInfo }))
209+ s := New (client , logger )
210+
211+ ctx := context .Background ()
212+ threadURL := "https://advrider.com/f/threads/electric-motorcycle-scooter-news-updates.1154248/"
213+
214+ // Fetch the latest post
215+ post , title , err := s .LatestPost (ctx , threadURL )
216+ if err != nil {
217+ t .Fatalf ("Failed to fetch Electric Motorcycle thread: %v" , err )
218+ }
219+
220+ // Validate thread title
221+ t .Logf ("Thread title: %q" , title )
222+ if title == "" {
223+ t .Error ("Thread title should not be empty" )
224+ }
225+
226+ // Validate post ID
227+ if post .ID == "" {
228+ t .Fatal ("Latest post should have an ID" )
229+ }
230+ t .Logf ("Latest post ID: %s" , post .ID )
231+
232+ // Validate post timestamp - THIS IS THE CRITICAL CHECK
233+ if post .Timestamp == "" {
234+ t .Fatal ("Latest post should have a timestamp" )
235+ }
236+ t .Logf ("Latest post timestamp: %s" , post .Timestamp )
237+
238+ // Validate timestamp can be parsed as RFC3339
239+ parsedTime , err := time .Parse (time .RFC3339 , post .Timestamp )
240+ if err != nil {
241+ t .Fatalf ("Failed to parse timestamp %q as RFC3339: %v" , post .Timestamp , err )
242+ }
243+ t .Logf ("Parsed timestamp: %s" , parsedTime .Format (time .RFC3339 ))
244+
245+ // Validate timestamp is not zero and is reasonable (not in the future, not too old)
246+ now := time .Now ()
247+ if parsedTime .IsZero () {
248+ t .Error ("Parsed timestamp should not be zero" )
249+ }
250+ if parsedTime .After (now ) {
251+ t .Errorf ("Parsed timestamp %s is in the future (now: %s)" , parsedTime , now )
252+ }
253+ // Electric motorcycle thread started in 2013, so posts should be after that
254+ if parsedTime .Year () < 2013 {
255+ t .Errorf ("Parsed timestamp %s is before thread creation (2013)" , parsedTime )
256+ }
257+
258+ // Validate other post fields
259+ if post .Author == "" {
260+ t .Error ("Latest post should have an author" )
261+ }
262+ if post .Content == "" {
263+ t .Error ("Latest post should have content" )
264+ }
265+ t .Logf ("Latest post by %s, content length: %d bytes" , post .Author , len (post .Content ))
111266}
0 commit comments