A community based topic aggregation platform built on atproto
1package unfurl
2
3import (
4 "context"
5 "net/http"
6 "net/http/httptest"
7 "testing"
8 "time"
9
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12)
13
14func TestFetchKagiKite_Success(t *testing.T) {
15 // Mock Kagi HTML response
16 mockHTML := `<!DOCTYPE html>
17<html>
18<head>
19 <title>FAA orders 10% flight cuts at 40 airports - Kagi News</title>
20 <meta property="og:title" content="FAA orders 10% flight cuts" />
21 <meta property="og:description" content="Flight restrictions announced" />
22</head>
23<body>
24 <img src="https://kagiproxy.com/img/DHdCvN_NqVDWU3UyoNZSv86b" alt="Airport runway" />
25</body>
26</html>`
27
28 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
29 w.Header().Set("Content-Type", "text/html")
30 w.WriteHeader(http.StatusOK)
31 _, _ = w.Write([]byte(mockHTML))
32 }))
33 defer server.Close()
34
35 ctx := context.Background()
36
37 result, err := fetchKagiKite(ctx, server.URL, 5*time.Second, "TestBot/1.0")
38
39 require.NoError(t, err)
40 assert.Equal(t, "article", result.Type)
41 assert.Equal(t, "FAA orders 10% flight cuts", result.Title)
42 assert.Equal(t, "Flight restrictions announced", result.Description)
43 assert.Contains(t, result.ThumbnailURL, "kagiproxy.com")
44 assert.Equal(t, "kagi", result.Provider)
45 assert.Equal(t, "kite.kagi.com", result.Domain)
46}
47
48func TestFetchKagiKite_NoImage(t *testing.T) {
49 mockHTML := `<!DOCTYPE html>
50<html>
51<head><title>Test Story</title></head>
52<body><p>No images here</p></body>
53</html>`
54
55 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
56 w.Header().Set("Content-Type", "text/html")
57 w.WriteHeader(http.StatusOK)
58 _, _ = w.Write([]byte(mockHTML))
59 }))
60 defer server.Close()
61
62 ctx := context.Background()
63
64 result, err := fetchKagiKite(ctx, server.URL, 5*time.Second, "TestBot/1.0")
65
66 assert.Error(t, err)
67 assert.Nil(t, result)
68 assert.Contains(t, err.Error(), "no image found")
69}
70
71func TestFetchKagiKite_FallbackToTitle(t *testing.T) {
72 mockHTML := `<!DOCTYPE html>
73<html>
74<head><title>Fallback Title</title></head>
75<body>
76 <img src="https://kagiproxy.com/img/test123" />
77</body>
78</html>`
79
80 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
81 w.Header().Set("Content-Type", "text/html")
82 w.WriteHeader(http.StatusOK)
83 _, _ = w.Write([]byte(mockHTML))
84 }))
85 defer server.Close()
86
87 ctx := context.Background()
88
89 result, err := fetchKagiKite(ctx, server.URL, 5*time.Second, "TestBot/1.0")
90
91 require.NoError(t, err)
92 assert.Equal(t, "Fallback Title", result.Title)
93 assert.Contains(t, result.ThumbnailURL, "kagiproxy.com")
94}
95
96func TestFetchKagiKite_ImageWithAltText(t *testing.T) {
97 mockHTML := `<!DOCTYPE html>
98<html>
99<head><title>News Story</title></head>
100<body>
101 <img src="https://kagiproxy.com/img/xyz789" alt="This is the alt text description" />
102</body>
103</html>`
104
105 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
106 w.Header().Set("Content-Type", "text/html")
107 w.WriteHeader(http.StatusOK)
108 _, _ = w.Write([]byte(mockHTML))
109 }))
110 defer server.Close()
111
112 ctx := context.Background()
113
114 result, err := fetchKagiKite(ctx, server.URL, 5*time.Second, "TestBot/1.0")
115
116 require.NoError(t, err)
117 assert.Equal(t, "News Story", result.Title)
118 assert.Equal(t, "This is the alt text description", result.Description)
119 assert.Contains(t, result.ThumbnailURL, "kagiproxy.com")
120}
121
122func TestFetchKagiKite_HTTPError(t *testing.T) {
123 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
124 w.WriteHeader(http.StatusNotFound)
125 }))
126 defer server.Close()
127
128 ctx := context.Background()
129
130 result, err := fetchKagiKite(ctx, server.URL, 5*time.Second, "TestBot/1.0")
131
132 assert.Error(t, err)
133 assert.Nil(t, result)
134 assert.Contains(t, err.Error(), "HTTP 404")
135}
136
137func TestFetchKagiKite_Timeout(t *testing.T) {
138 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
139 time.Sleep(2 * time.Second)
140 w.WriteHeader(http.StatusOK)
141 }))
142 defer server.Close()
143
144 ctx := context.Background()
145
146 result, err := fetchKagiKite(ctx, server.URL, 100*time.Millisecond, "TestBot/1.0")
147
148 assert.Error(t, err)
149 assert.Nil(t, result)
150}
151
152func TestFetchKagiKite_MultipleImages_PicksSecond(t *testing.T) {
153 mockHTML := `<!DOCTYPE html>
154<html>
155<head><title>Story with multiple images</title></head>
156<body>
157 <img src="https://kagiproxy.com/img/first123" alt="First image (header/logo)" />
158 <img src="https://kagiproxy.com/img/second456" alt="Second image" />
159</body>
160</html>`
161
162 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
163 w.Header().Set("Content-Type", "text/html")
164 w.WriteHeader(http.StatusOK)
165 _, _ = w.Write([]byte(mockHTML))
166 }))
167 defer server.Close()
168
169 ctx := context.Background()
170
171 result, err := fetchKagiKite(ctx, server.URL, 5*time.Second, "TestBot/1.0")
172
173 require.NoError(t, err)
174 // We skip the first image (often a header/logo) and use the second
175 assert.Contains(t, result.ThumbnailURL, "second456")
176 assert.Equal(t, "Second image", result.Description)
177}
178
179func TestFetchKagiKite_OnlyNonKagiImages_NoMatch(t *testing.T) {
180 mockHTML := `<!DOCTYPE html>
181<html>
182<head><title>Story with non-Kagi images</title></head>
183<body>
184 <img src="https://example.com/img/test.jpg" alt="External image" />
185</body>
186</html>`
187
188 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
189 w.Header().Set("Content-Type", "text/html")
190 w.WriteHeader(http.StatusOK)
191 _, _ = w.Write([]byte(mockHTML))
192 }))
193 defer server.Close()
194
195 ctx := context.Background()
196
197 result, err := fetchKagiKite(ctx, server.URL, 5*time.Second, "TestBot/1.0")
198
199 assert.Error(t, err)
200 assert.Nil(t, result)
201 assert.Contains(t, err.Error(), "no image found")
202}