main.TestHandleCompletedPart   C
last analyzed

Complexity

Conditions 11

Size

Total Lines 52
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 34
nop 1
dl 0
loc 52
rs 5.4
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like main.TestHandleCompletedPart often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
package main
2
3
import (
4
	"os"
5
	"os/user"
6
	"path/filepath"
7
	"strings"
8
	"testing"
9
	"time"
10
)
11
12
func TestPartCalculate(t *testing.T) {
13
	// Disable progress bar for tests
14
	displayProgress = false
15
16
	// Setup test environment
17
	originalDataFolder := dataFolder
18
	dataFolder = ".hget_test/"
19
	defer func() {
20
		dataFolder = originalDataFolder
21
		usr, _ := user.Current()
22
		testFolder := filepath.Join(usr.HomeDir, dataFolder)
23
		os.RemoveAll(testFolder)
24
	}()
25
26
	// Test with different numbers of parts
27
	testCases := []struct {
28
		parts       int64
29
		totalSize   int64
30
		url         string
31
		expectParts int
32
	}{
33
		{10, 100, "http://foo.bar/file", 10},
34
		{5, 1000, "http://example.com/largefile", 5},
35
		{1, 50, "http://test.org/smallfile", 1},
36
		{3, 10, "http://tiny.file/data", 3}, // Small file, multiple parts
37
	}
38
39
	for _, tc := range testCases {
40
		parts := partCalculate(tc.parts, tc.totalSize, tc.url)
41
42
		// Check number of parts
43
		if len(parts) != tc.expectParts {
44
			t.Errorf("Expected %d parts, got %d", tc.expectParts, len(parts))
45
		}
46
47
		// Check part URLs
48
		for i, part := range parts {
49
			if part.URL != tc.url {
50
				t.Errorf("Part %d: Expected URL %s, got %s", i, tc.url, part.URL)
51
			}
52
53
			// Check part index
54
			if part.Index != int64(i) {
55
				t.Errorf("Part %d: Expected Index %d, got %d", i, i, part.Index)
56
			}
57
58
			// Check ranges
59
			expectedSize := tc.totalSize / tc.parts
60
			if i < int(tc.parts-1) {
61
				if part.RangeFrom != expectedSize*int64(i) {
62
					t.Errorf("Part %d: Expected RangeFrom %d, got %d",
63
						i, expectedSize*int64(i), part.RangeFrom)
64
				}
65
				if part.RangeTo != expectedSize*int64(i+1)-1 {
66
					t.Errorf("Part %d: Expected RangeTo %d, got %d",
67
						i, expectedSize*int64(i+1)-1, part.RangeTo)
68
				}
69
			} else {
70
				// Last part might be larger due to division remainder
71
				if part.RangeFrom != expectedSize*int64(i) {
72
					t.Errorf("Part %d: Expected RangeFrom %d, got %d",
73
						i, expectedSize*int64(i), part.RangeFrom)
74
				}
75
				if part.RangeTo != tc.totalSize {
76
					t.Errorf("Part %d: Expected RangeTo %d, got %d",
77
						i, tc.totalSize, part.RangeTo)
78
				}
79
			}
80
81
			// Check path format
82
			usr, _ := user.Current()
83
			expectedBasePath := filepath.Join(usr.HomeDir, dataFolder)
84
			if !strings.Contains(part.Path, expectedBasePath) {
85
				t.Errorf("Part %d: Path does not contain expected base path: %s", i, part.Path)
86
			}
87
88
			fileName := filepath.Base(part.Path)
89
			expectedPrefix := TaskFromURL(tc.url) + ".part"
90
			if !strings.HasPrefix(fileName, expectedPrefix) {
91
				t.Errorf("Part %d: Expected filename prefix %s, got %s",
92
					i, expectedPrefix, fileName)
93
			}
94
		}
95
	}
96
}
97
98
func TestProxyAwareHTTPClient(t *testing.T) {
99
	// Test with no proxy
100
	client := ProxyAwareHTTPClient("", false)
101
	if client == nil {
102
		t.Fatal("ProxyAwareHTTPClient returned nil with no proxy")
103
	}
104
105
	// Cannot easily test with an actual proxy, but can verify it doesn't crash
106
	httpProxyClient := ProxyAwareHTTPClient("http://localhost:8080", false)
107
	if httpProxyClient == nil {
108
		t.Fatal("ProxyAwareHTTPClient returned nil with HTTP proxy")
109
	}
110
111
	socksProxyClient := ProxyAwareHTTPClient("localhost:1080", false)
112
	if socksProxyClient == nil {
113
		t.Fatal("ProxyAwareHTTPClient returned nil with SOCKS proxy")
114
	}
115
116
	// Test TLS skipVerify parameter
117
	tlsClient := ProxyAwareHTTPClient("", true)
118
	if tlsClient == nil {
119
		t.Fatal("ProxyAwareHTTPClient returned nil with TLS skip verification")
120
	}
121
122
	// Can't directly access TLS config, but it shouldn't crash
123
}
124
125
// Helper function to parse integers
126
func parseInt(s string) int {
127
	n := 0
128
	for _, c := range s {
129
		n = n*10 + int(c-'0')
130
	}
131
	return n
132
}
133
134
func TestHandleCompletedPart(t *testing.T) {
135
	// Disable progress bar for tests
136
	displayProgress = false
137
138
	// Create a part that's already complete
139
	part := Part{
140
		Index:     0,
141
		URL:       "http://example.com/test",
142
		Path:      "test.part000000",
143
		RangeFrom: 100, // RangeFrom equals RangeTo means no data to download
144
		RangeTo:   100,
145
	}
146
147
	// Create channels
148
	fileChan := make(chan string, 1)
149
	stateSaveChan := make(chan Part, 1)
150
151
	// Create downloader
152
	downloader := &HTTPDownloader{
153
		url:       "http://example.com/test",
154
		file:      "test",
155
		par:       1,
156
		len:       100,
157
		parts:     []Part{part},
158
		resumable: true,
159
	}
160
161
	// Handle the completed part
162
	downloader.handleCompletedPart(part, fileChan, stateSaveChan)
163
164
	// Verify the path was sent to fileChan
165
	select {
166
	case path := <-fileChan:
167
		if path != part.Path {
168
			t.Errorf("Expected path %s, got %s", part.Path, path)
169
		}
170
	default:
171
		t.Errorf("No path sent to fileChan")
172
	}
173
174
	// Verify the part was sent to stateSaveChan
175
	select {
176
	case savedPart := <-stateSaveChan:
177
		if savedPart.Index != part.Index ||
178
			savedPart.URL != part.URL ||
179
			savedPart.Path != part.Path ||
180
			savedPart.RangeFrom != part.RangeFrom ||
181
			savedPart.RangeTo != part.RangeTo {
182
			t.Errorf("Saved part does not match original part")
183
		}
184
	default:
185
		t.Errorf("No part sent to stateSaveChan")
186
	}
187
}
188
189
func TestBuildRequestForPart(t *testing.T) {
190
	// Test cases for different range situations
191
	testCases := []struct {
192
		description string
193
		part        Part
194
		contentLen  int64
195
		parallelism int64
196
		expected    string
197
	}{
198
		{
199
			description: "Single connection download (no range)",
200
			part: Part{
201
				Index:     0,
202
				URL:       "http://example.com/file",
203
				Path:      "file.part000000",
204
				RangeFrom: 0,
205
				RangeTo:   100,
206
			},
207
			contentLen:  100,
208
			parallelism: 1,
209
			expected:    "", // No range header expected
210
		},
211
		{
212
			description: "Multiple connection download with middle part",
213
			part: Part{
214
				Index:     1,
215
				URL:       "http://example.com/file",
216
				Path:      "file.part000001",
217
				RangeFrom: 50,
218
				RangeTo:   99,
219
			},
220
			contentLen:  200,
221
			parallelism: 3,
222
			expected:    "bytes=50-99",
223
		},
224
		{
225
			description: "Multiple connection download with last part",
226
			part: Part{
227
				Index:     2,
228
				URL:       "http://example.com/file",
229
				Path:      "file.part000002",
230
				RangeFrom: 100,
231
				RangeTo:   200,
232
			},
233
			contentLen:  200,
234
			parallelism: 3,
235
			expected:    "bytes=100-",
236
		},
237
	}
238
239
	for _, tc := range testCases {
240
		t.Run(tc.description, func(t *testing.T) {
241
			// Create downloader
242
			downloader := &HTTPDownloader{
243
				url:       tc.part.URL,
244
				file:      "file",
245
				par:       tc.parallelism,
246
				len:       tc.contentLen,
247
				parts:     []Part{tc.part},
248
				resumable: true,
249
			}
250
251
			// Build request
252
			req, err := downloader.buildRequestForPart(tc.part)
253
			if err != nil {
254
				t.Fatalf("buildRequestForPart failed: %v", err)
255
			}
256
257
			// Check range header
258
			rangeHeader := req.Header.Get("Range")
259
			if tc.expected == "" {
260
				if rangeHeader != "" {
261
					t.Errorf("Expected no Range header, got '%s'", rangeHeader)
262
				}
263
			} else {
264
				if rangeHeader != tc.expected {
265
					t.Errorf("Expected Range header '%s', got '%s'", tc.expected, rangeHeader)
266
				}
267
			}
268
269
			// Check URL
270
			if req.URL.String() != tc.part.URL {
271
				t.Errorf("Expected URL %s, got %s", tc.part.URL, req.URL.String())
272
			}
273
		})
274
	}
275
}
276
277
func TestCopyContent(t *testing.T) {
278
	// Create test data
279
	testData := "This is test data for copy content"
280
281
	// Test regular copy (no rate limit)
282
	t.Run("No Rate Limit", func(t *testing.T) {
283
		// Create source and destination
284
		src := strings.NewReader(testData)
285
		var dst strings.Builder
286
287
		// Create downloader with no rate limit
288
		downloader := &HTTPDownloader{
289
			rate: 0,
290
		}
291
292
		// Copy content
293
		done := make(chan bool)
294
		go downloader.copyContent(src, &dst, done)
295
296
		// Wait for completion
297
		<-done
298
299
		// Verify copied content
300
		if dst.String() != testData {
301
			t.Errorf("Expected content '%s', got '%s'", testData, dst.String())
302
		}
303
	})
304
305
	// Test copy with rate limit (can only test that it doesn't crash)
306
	t.Run("With Rate Limit", func(t *testing.T) {
307
		// Create source and destination
308
		src := strings.NewReader(testData)
309
		var dst strings.Builder
310
311
		// Create downloader with rate limit
312
		downloader := &HTTPDownloader{
313
			rate: 1024, // 1KB/s
314
		}
315
316
		// Copy content
317
		done := make(chan bool)
318
		go downloader.copyContent(src, &dst, done)
319
320
		// Wait for completion with timeout
321
		select {
322
		case <-done:
323
			// Success
324
		case <-time.After(2 * time.Second):
325
			t.Fatalf("Copy with rate limit timed out")
326
		}
327
328
		// Verify copied content
329
		if dst.String() != testData {
330
			t.Errorf("Expected content '%s', got '%s'", testData, dst.String())
331
		}
332
	})
333
}
334