Skip to content

Commit cb57b15

Browse files
committed
test(api): add tests for robots.txt and OpenAPI spec endpoints
Added unit tests for the robots.txt and OpenAPI specification endpoints to ensure correct responses and content types. This improves test coverage and verifies the expected behavior of the API.
1 parent ac0c0d6 commit cb57b15

File tree

5 files changed

+987
-18
lines changed

5 files changed

+987
-18
lines changed

cmd/server/api_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,3 +390,114 @@ func TestHandleGetHeaders_MissingParams(t *testing.T) {
390390
t.Errorf("Expected status 'error', got '%s'", response.Status)
391391
}
392392
}
393+
394+
func TestHandleRobots(t *testing.T) {
395+
app, _ := setupTestApp(t)
396+
397+
req := httptest.NewRequest("GET", "/robots.txt", nil)
398+
resp, err := app.Test(req)
399+
if err != nil {
400+
t.Fatalf("Failed to make request: %v", err)
401+
}
402+
defer func() { _ = resp.Body.Close() }()
403+
404+
if resp.StatusCode != 200 {
405+
t.Errorf("Expected status 200, got %d", resp.StatusCode)
406+
}
407+
408+
contentType := resp.Header.Get("Content-Type")
409+
if contentType != "text/plain" {
410+
t.Errorf("Expected Content-Type 'text/plain', got '%s'", contentType)
411+
}
412+
413+
body, _ := io.ReadAll(resp.Body)
414+
expected := "User-agent: *\nDisallow: /\n"
415+
if string(body) != expected {
416+
t.Errorf("Expected robots.txt content '%s', got '%s'", expected, string(body))
417+
}
418+
}
419+
420+
func TestHandleOpenAPISpec(t *testing.T) {
421+
app, _ := setupTestApp(t)
422+
423+
req := httptest.NewRequest("GET", "/openapi.yaml", nil)
424+
resp, err := app.Test(req)
425+
if err != nil {
426+
t.Fatalf("Failed to make request: %v", err)
427+
}
428+
defer func() { _ = resp.Body.Close() }()
429+
430+
if resp.StatusCode != 200 {
431+
t.Errorf("Expected status 200, got %d", resp.StatusCode)
432+
}
433+
434+
contentType := resp.Header.Get("Content-Type")
435+
if contentType != "application/yaml" {
436+
t.Errorf("Expected Content-Type 'application/yaml', got '%s'", contentType)
437+
}
438+
439+
body, _ := io.ReadAll(resp.Body)
440+
if len(body) == 0 {
441+
t.Error("Expected non-empty OpenAPI spec")
442+
}
443+
444+
// Check that it starts with openapi version
445+
bodyStr := string(body)
446+
if len(bodyStr) < 10 || bodyStr[0:8] != "openapi:" {
447+
t.Error("Expected OpenAPI spec to start with 'openapi:'")
448+
}
449+
}
450+
451+
func TestHandleSwaggerUI(t *testing.T) {
452+
app, _ := setupTestApp(t)
453+
454+
req := httptest.NewRequest("GET", "/docs", nil)
455+
resp, err := app.Test(req)
456+
if err != nil {
457+
t.Fatalf("Failed to make request: %v", err)
458+
}
459+
defer func() { _ = resp.Body.Close() }()
460+
461+
if resp.StatusCode != 200 {
462+
t.Errorf("Expected status 200, got %d", resp.StatusCode)
463+
}
464+
465+
contentType := resp.Header.Get("Content-Type")
466+
if contentType != "text/html" {
467+
t.Errorf("Expected Content-Type 'text/html', got '%s'", contentType)
468+
}
469+
470+
body, _ := io.ReadAll(resp.Body)
471+
bodyStr := string(body)
472+
473+
// Check for key Swagger UI elements
474+
if !containsString(bodyStr, "<!DOCTYPE html>") {
475+
t.Error("Expected HTML doctype in Swagger UI")
476+
}
477+
478+
if !containsString(bodyStr, "swagger-ui") {
479+
t.Error("Expected 'swagger-ui' in Swagger UI HTML")
480+
}
481+
482+
if !containsString(bodyStr, "Chaintracks API Documentation") {
483+
t.Error("Expected 'Chaintracks API Documentation' title in Swagger UI")
484+
}
485+
486+
if !containsString(bodyStr, "/openapi.yaml") {
487+
t.Error("Expected reference to '/openapi.yaml' in Swagger UI")
488+
}
489+
}
490+
491+
// Helper function for string contains check
492+
func containsString(s, substr string) bool {
493+
return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && containsSubstring(s, substr))
494+
}
495+
496+
func containsSubstring(s, substr string) bool {
497+
for i := 0; i <= len(s)-len(substr); i++ {
498+
if s[i:i+len(substr)] == substr {
499+
return true
500+
}
501+
}
502+
return false
503+
}

cmd/server/dashboard_test.go

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
package main
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/bsv-blockchain/go-chaintracks/pkg/chaintracks"
11+
)
12+
13+
func TestDashboardHandlerRenderPeerList(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
peers []chaintracks.PeerInfo
17+
expectContains []string
18+
expectNotContains []string
19+
}{
20+
{
21+
name: "EmptyPeerList",
22+
peers: []chaintracks.PeerInfo{},
23+
expectContains: []string{
24+
"No peers connected",
25+
"color: #808080",
26+
"font-style: italic",
27+
},
28+
expectNotContains: []string{
29+
"<div class=\"peer\">",
30+
},
31+
},
32+
{
33+
name: "NilPeerList",
34+
peers: nil,
35+
expectContains: []string{
36+
"No peers connected",
37+
"color: #808080",
38+
},
39+
expectNotContains: []string{
40+
"<div class=\"peer\">",
41+
},
42+
},
43+
{
44+
name: "SinglePeerWithValidName",
45+
peers: []chaintracks.PeerInfo{
46+
{
47+
ID: "QmPeer12345",
48+
Name: "TestNode",
49+
Addrs: []string{"/ip4/192.168.1.1/tcp/4001"},
50+
},
51+
},
52+
expectContains: []string{
53+
"<div class=\"peer\">",
54+
"<strong>TestNode</strong>",
55+
"QmPeer12345",
56+
`<div class="peer-id">QmPeer12345</div>`,
57+
`<div class="peer-addr">/ip4/192.168.1.1/tcp/4001</div>`,
58+
},
59+
expectNotContains: []string{
60+
"No peers connected",
61+
"Unknown Peer",
62+
},
63+
},
64+
{
65+
name: "MultiplePeers",
66+
peers: []chaintracks.PeerInfo{
67+
{
68+
ID: "QmPeer1",
69+
Name: "NodeOne",
70+
Addrs: []string{"/ip4/192.168.1.1/tcp/4001"},
71+
},
72+
{
73+
ID: "QmPeer2",
74+
Name: "NodeTwo",
75+
Addrs: []string{"/ip4/192.168.1.2/tcp/4001"},
76+
},
77+
{
78+
ID: "QmPeer3",
79+
Name: "NodeThree",
80+
Addrs: []string{"/ip4/192.168.1.3/tcp/4001"},
81+
},
82+
},
83+
expectContains: []string{
84+
"<strong>NodeOne</strong>",
85+
"<strong>NodeTwo</strong>",
86+
"<strong>NodeThree</strong>",
87+
"QmPeer1",
88+
"QmPeer2",
89+
"QmPeer3",
90+
},
91+
expectNotContains: []string{
92+
"No peers connected",
93+
},
94+
},
95+
{
96+
name: "PeerWithUnknownName",
97+
peers: []chaintracks.PeerInfo{
98+
{
99+
ID: "QmPeer123",
100+
Name: "unknown",
101+
Addrs: []string{"/ip4/192.168.1.1/tcp/4001"},
102+
},
103+
},
104+
expectContains: []string{
105+
"<strong>Unknown Peer</strong>",
106+
"QmPeer123",
107+
},
108+
expectNotContains: []string{
109+
"<strong>unknown</strong>",
110+
},
111+
},
112+
{
113+
name: "PeerWithEmptyName",
114+
peers: []chaintracks.PeerInfo{
115+
{
116+
ID: "QmPeer456",
117+
Name: "",
118+
Addrs: []string{"/ip4/192.168.1.1/tcp/4001"},
119+
},
120+
},
121+
expectContains: []string{
122+
"<strong>Unknown Peer</strong>",
123+
"QmPeer456",
124+
},
125+
expectNotContains: []string{
126+
"<strong></strong>",
127+
},
128+
},
129+
{
130+
name: "PeerWithMultipleAddresses",
131+
peers: []chaintracks.PeerInfo{
132+
{
133+
ID: "QmPeerMulti",
134+
Name: "MultiAddrNode",
135+
Addrs: []string{
136+
"/ip4/192.168.1.1/tcp/4001",
137+
"/ip6/::1/tcp/4001",
138+
"/dns4/example.com/tcp/4001",
139+
},
140+
},
141+
},
142+
expectContains: []string{
143+
"<strong>MultiAddrNode</strong>",
144+
"QmPeerMulti",
145+
`<div class="peer-addr">/ip4/192.168.1.1/tcp/4001</div>`,
146+
`<div class="peer-addr">/ip6/::1/tcp/4001</div>`,
147+
`<div class="peer-addr">/dns4/example.com/tcp/4001</div>`,
148+
},
149+
expectNotContains: nil,
150+
},
151+
{
152+
name: "PeerWithNoAddresses",
153+
peers: []chaintracks.PeerInfo{
154+
{
155+
ID: "QmPeerNoAddr",
156+
Name: "NoAddressNode",
157+
Addrs: []string{},
158+
},
159+
},
160+
expectContains: []string{
161+
"<strong>NoAddressNode</strong>",
162+
"QmPeerNoAddr",
163+
},
164+
expectNotContains: []string{
165+
"<div class=\"peer-addr\">",
166+
},
167+
},
168+
{
169+
name: "MixedPeerNames",
170+
peers: []chaintracks.PeerInfo{
171+
{
172+
ID: "QmPeer1",
173+
Name: "ValidName",
174+
Addrs: []string{"/ip4/192.168.1.1/tcp/4001"},
175+
},
176+
{
177+
ID: "QmPeer2",
178+
Name: "unknown",
179+
Addrs: []string{"/ip4/192.168.1.2/tcp/4001"},
180+
},
181+
{
182+
ID: "QmPeer3",
183+
Name: "",
184+
Addrs: []string{"/ip4/192.168.1.3/tcp/4001"},
185+
},
186+
},
187+
expectContains: []string{
188+
"<strong>ValidName</strong>",
189+
"<strong>Unknown Peer</strong>",
190+
"QmPeer1",
191+
"QmPeer2",
192+
"QmPeer3",
193+
},
194+
expectNotContains: nil,
195+
},
196+
}
197+
198+
for _, tt := range tests {
199+
t.Run(tt.name, func(t *testing.T) {
200+
handler := &DashboardHandler{}
201+
202+
result := handler.renderPeerList(tt.peers)
203+
204+
require.NotEmpty(t, result, "renderPeerList should not return empty string")
205+
206+
// Check for expected content
207+
for _, expected := range tt.expectContains {
208+
assert.Contains(t, result, expected,
209+
"Expected result to contain: %s", expected)
210+
}
211+
212+
// Check for unexpected content
213+
for _, notExpected := range tt.expectNotContains {
214+
assert.NotContains(t, result, notExpected,
215+
"Expected result to NOT contain: %s", notExpected)
216+
}
217+
218+
// Additional validation for empty peer list
219+
if len(tt.peers) == 0 {
220+
// Should return exactly the "no peers" message
221+
assert.Equal(t, `<div style="color: #808080; font-style: italic;">No peers connected</div>`, result)
222+
}
223+
224+
// Additional validation for non-empty peer list
225+
if len(tt.peers) > 0 {
226+
// Should contain a peer div for each peer
227+
peerDivCount := strings.Count(result, `<div class="peer">`)
228+
assert.Equal(t, len(tt.peers), peerDivCount,
229+
"Should have one peer div for each peer")
230+
}
231+
})
232+
}
233+
}

0 commit comments

Comments
 (0)