-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathintegration_test.go
More file actions
201 lines (161 loc) · 6.42 KB
/
Copy pathintegration_test.go
File metadata and controls
201 lines (161 loc) · 6.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
//go:build integration
package domainfront
import (
"context"
"embed"
"io"
"net/http"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
//go:embed fronted.yaml.gz
var embeddedConfig embed.FS
func loadEmbeddedConfig(t *testing.T) *Config {
t.Helper()
data, err := embeddedConfig.ReadFile("fronted.yaml.gz")
require.NoError(t, err)
cfg, err := ParseConfig(data)
require.NoError(t, err)
return cfg
}
// TestIntegration_CloudFrontPing verifies that domain fronting works end-to-end
// with the real fronted.yaml.gz configuration and real CloudFront infrastructure.
// It fetches the CloudFront ping endpoint through domain fronting.
//
// Run with: go test -tags integration -run TestIntegration -timeout 120s -v
func TestIntegration_CloudFrontPing(t *testing.T) {
cfg := loadEmbeddedConfig(t)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
cacheFile := filepath.Join(t.TempDir(), "cache.json")
client, err := New(ctx, cfg,
WithCacheFile(cacheFile),
WithDefaultProviderID("cloudfront"),
)
require.NoError(t, err)
defer client.Close()
// Wait for at least one working front
require.Eventually(t, func() bool {
return client.pool.readyCount() > 0
}, 30*time.Second, 200*time.Millisecond, "should find at least one working front")
t.Logf("Found %d ready fronts", client.pool.readyCount())
// Fetch a real resource through CloudFront domain fronting.
// borda.lantern.io is mapped to d157vud77ygy87.cloudfront.net in the config.
rt := client.RoundTripper()
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
"https://borda.lantern.io/ping", nil)
require.NoError(t, err)
resp, err := rt.RoundTrip(req)
require.NoError(t, err, "round trip through CloudFront should succeed")
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
t.Logf("Response: status=%d bodyLen=%d", resp.StatusCode, len(body))
// The request reached the origin via domain fronting. We accept any
// non-server-error status — the important thing is the CDN didn't
// reject us (400/403) and we successfully tunneled through.
assert.Less(t, resp.StatusCode, 500, "should not get a server error")
assert.NotEqual(t, 400, resp.StatusCode, "should not get CDN 'invalid URL' error")
assert.NotEqual(t, 403, resp.StatusCode, "should not get CDN rejection")
}
// TestIntegration_AkamaiPing verifies domain fronting through Akamai.
// Skipped in CI because Akamai endpoints are often unreliable from CI runners.
func TestIntegration_AkamaiPing(t *testing.T) {
if os.Getenv("GITHUB_ACTIONS") == "true" {
t.Skip("Skipping Akamai integration test in CI: real Akamai endpoints are unreliable from CI runners")
}
cfg := loadEmbeddedConfig(t)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
client, err := New(ctx, cfg,
WithDefaultProviderID("akamai"),
)
require.NoError(t, err)
defer client.Close()
require.Eventually(t, func() bool {
return client.pool.readyCount() > 0
}, 30*time.Second, 200*time.Millisecond, "should find at least one working Akamai front")
t.Logf("Found %d ready fronts", client.pool.readyCount())
rt := client.RoundTripper()
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
"https://borda.lantern.io/ping", nil)
require.NoError(t, err)
resp, err := rt.RoundTrip(req)
require.NoError(t, err, "round trip through Akamai should succeed")
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
t.Logf("Response: status=%d bodyLen=%d", resp.StatusCode, len(body))
assert.Less(t, resp.StatusCode, 500, "should not get a server error")
assert.NotEqual(t, 400, resp.StatusCode, "should not get CDN 'invalid URL' error")
assert.NotEqual(t, 403, resp.StatusCode, "should not get CDN rejection")
}
// TestIntegration_CachePersistence verifies that working fronts survive
// across client restarts via the cache file.
func TestIntegration_CachePersistence(t *testing.T) {
cfg := loadEmbeddedConfig(t)
cacheFile := filepath.Join(t.TempDir(), "cache.json")
// First run: discover working fronts
ctx1, cancel1 := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel1()
client1, err := New(ctx1, cfg,
WithCacheFile(cacheFile),
WithDefaultProviderID("cloudfront"),
)
require.NoError(t, err)
require.Eventually(t, func() bool {
return client1.pool.readyCount() > 0
}, 30*time.Second, 200*time.Millisecond)
client1.Close()
// Verify cache file was written
data, err := os.ReadFile(cacheFile)
require.NoError(t, err)
assert.Greater(t, len(data), 10, "cache file should have content")
t.Logf("Cache file size: %d bytes", len(data))
// Second run: should start faster thanks to cache
ctx2, cancel2 := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel2()
start := time.Now()
client2, err := New(ctx2, cfg,
WithCacheFile(cacheFile),
WithDefaultProviderID("cloudfront"),
)
require.NoError(t, err)
defer client2.Close()
require.Eventually(t, func() bool {
return client2.pool.readyCount() > 0
}, 30*time.Second, 200*time.Millisecond)
elapsed := time.Since(start)
t.Logf("Second startup with cache: %v to first working front", elapsed)
}
// TestIntegration_NewConnectedRoundTripper verifies that NewConnectedRoundTripper
// blocks until a front is actually dialed (TLS handshake complete) and that
// the returned one-shot RoundTripper can satisfy a real domain-fronted request.
func TestIntegration_NewConnectedRoundTripper(t *testing.T) {
cfg := loadEmbeddedConfig(t)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
client, err := New(ctx, cfg,
WithCacheFile(filepath.Join(t.TempDir(), "cache.json")),
WithDefaultProviderID("cloudfront"),
)
require.NoError(t, err)
defer client.Close()
rt, err := client.NewConnectedRoundTripper(ctx, "")
require.NoError(t, err, "NewConnectedRoundTripper should block until a front is dialed")
require.NotNil(t, rt)
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
"https://borda.lantern.io/ping", nil)
require.NoError(t, err)
resp, err := rt.RoundTrip(req)
require.NoError(t, err, "round trip on pre-connected RT should succeed")
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
require.NoError(t, err, "reading response body should succeed")
t.Logf("Response: status=%d bodyLen=%d", resp.StatusCode, len(body))
assert.Less(t, resp.StatusCode, 500)
assert.NotEqual(t, 400, resp.StatusCode)
assert.NotEqual(t, 403, resp.StatusCode)
}