mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-08 09:30:27 +00:00
feat: implement task patching functionality (#1267)
This commit is contained in:
22 files changed
+1281
-132
No files matched your search
@@ -232,6 +232,59 @@ func (f *Fetcher) isDone() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Patch modifies the BT task settings.
|
||||
// Invalid file indices are silently ignored.
|
||||
func (f *Fetcher) Patch(req *base.Request, opts *base.Options) error {
|
||||
if opts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if opts.SelectFiles != nil {
|
||||
selectFiles := opts.SelectFiles
|
||||
|
||||
// Get file count from resource metadata
|
||||
fileCount := 0
|
||||
if f.meta.Res != nil {
|
||||
fileCount = len(f.meta.Res.Files)
|
||||
}
|
||||
|
||||
// Filter out invalid indices (silently ignore)
|
||||
validSelectFiles := make([]int, 0, len(selectFiles))
|
||||
for _, idx := range selectFiles {
|
||||
if idx >= 0 && idx < fileCount {
|
||||
validSelectFiles = append(validSelectFiles, idx)
|
||||
}
|
||||
}
|
||||
|
||||
if f.torrent != nil {
|
||||
files := f.torrent.Files()
|
||||
|
||||
// Cancel all current file downloads first
|
||||
f.torrent.CancelPieces(0, f.torrent.NumPieces())
|
||||
|
||||
// Apply new file selection
|
||||
if len(validSelectFiles) == len(files) {
|
||||
f.torrent.DownloadAll()
|
||||
} else {
|
||||
for _, selectIndex := range validSelectFiles {
|
||||
file := files[selectIndex]
|
||||
file.Download()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f.meta.Opts.SelectFiles = validSelectFiles
|
||||
// Recalculate the resource size based on new selection
|
||||
if f.meta.Res != nil {
|
||||
f.meta.Res.CalcSize(validSelectFiles)
|
||||
}
|
||||
// Reset progress tracking for new file selection
|
||||
f.data.Progress = make(fetcher.Progress, len(validSelectFiles))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Fetcher) updateRes() {
|
||||
res := &base.Resource{
|
||||
Range: true,
|
||||
|
||||
@@ -239,3 +239,130 @@ func buildConfigFetcher(proxyConfig *base.DownloaderProxyConfig) fetcher.Fetcher
|
||||
fetcher.Setup(newController)
|
||||
return fetcher
|
||||
}
|
||||
|
||||
// TestFetcher_Patch tests the Patch functionality for BT fetcher.
|
||||
// It tests modifying selected files after Resolve (without downloading).
|
||||
func TestFetcher_Patch(t *testing.T) {
|
||||
f := buildFetcher()
|
||||
|
||||
// Resolve a multi-file torrent
|
||||
err := f.Resolve(&base.Request{
|
||||
URL: "./testdata/test.torrent",
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify initial state: 3 files, all selected by default
|
||||
meta := f.Meta()
|
||||
if len(meta.Res.Files) != 3 {
|
||||
t.Fatalf("Expected 3 files, got %d", len(meta.Res.Files))
|
||||
}
|
||||
if len(meta.Opts.SelectFiles) != 3 {
|
||||
t.Fatalf("Expected 3 selected files, got %d", len(meta.Opts.SelectFiles))
|
||||
}
|
||||
|
||||
// Total size of all files: 107484864
|
||||
totalSize := int64(107484864)
|
||||
if meta.Res.Size != totalSize {
|
||||
t.Fatalf("Expected total size %d, got %d", totalSize, meta.Res.Size)
|
||||
}
|
||||
|
||||
t.Run("Patch with valid indices", func(t *testing.T) {
|
||||
// Select only file 0 and 2 (c.txt and a.txt)
|
||||
err := f.Patch(nil, &base.Options{
|
||||
SelectFiles: []int{0, 2},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Patch failed: %v", err)
|
||||
}
|
||||
|
||||
meta := f.Meta()
|
||||
if !reflect.DeepEqual(meta.Opts.SelectFiles, []int{0, 2}) {
|
||||
t.Errorf("Expected SelectFiles [0, 2], got %v", meta.Opts.SelectFiles)
|
||||
}
|
||||
|
||||
// Size should be recalculated: c.txt (98501754) + a.txt (78114) = 98579868
|
||||
expectedSize := int64(98501754 + 78114)
|
||||
if meta.Res.Size != expectedSize {
|
||||
t.Errorf("Expected size %d, got %d", expectedSize, meta.Res.Size)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Patch with invalid indices are silently ignored", func(t *testing.T) {
|
||||
// Mix of valid (0, 1) and invalid (-1, 5, 100) indices
|
||||
err := f.Patch(nil, &base.Options{
|
||||
SelectFiles: []int{-1, 0, 5, 1, 100},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Patch should not return error for invalid indices: %v", err)
|
||||
}
|
||||
|
||||
meta := f.Meta()
|
||||
// Only valid indices 0 and 1 should remain
|
||||
if !reflect.DeepEqual(meta.Opts.SelectFiles, []int{0, 1}) {
|
||||
t.Errorf("Expected SelectFiles [0, 1], got %v", meta.Opts.SelectFiles)
|
||||
}
|
||||
|
||||
// Size should be: c.txt (98501754) + b.txt (8904996) = 107406750
|
||||
expectedSize := int64(98501754 + 8904996)
|
||||
if meta.Res.Size != expectedSize {
|
||||
t.Errorf("Expected size %d, got %d", expectedSize, meta.Res.Size)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Patch with all invalid indices results in empty selection", func(t *testing.T) {
|
||||
err := f.Patch(nil, &base.Options{
|
||||
SelectFiles: []int{-5, 10, 999},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Patch should not return error: %v", err)
|
||||
}
|
||||
|
||||
meta := f.Meta()
|
||||
if len(meta.Opts.SelectFiles) != 0 {
|
||||
t.Errorf("Expected empty SelectFiles, got %v", meta.Opts.SelectFiles)
|
||||
}
|
||||
|
||||
// Note: CalcSize with empty selectFiles calculates total size of all files
|
||||
// This is by design - empty selection in CalcSize means "all files"
|
||||
// But SelectFiles being empty means no files are selected for download
|
||||
if meta.Res.Size != totalSize {
|
||||
t.Errorf("Expected size %d (CalcSize with empty slice = all files), got %d", totalSize, meta.Res.Size)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Patch with nil opts does nothing", func(t *testing.T) {
|
||||
// First set a known state
|
||||
f.Patch(nil, &base.Options{SelectFiles: []int{1}})
|
||||
prevSelectFiles := f.Meta().Opts.SelectFiles
|
||||
|
||||
// Patch with nil opts
|
||||
err := f.Patch(nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Patch with nil opts should not fail: %v", err)
|
||||
}
|
||||
|
||||
// Should remain unchanged
|
||||
if !reflect.DeepEqual(f.Meta().Opts.SelectFiles, prevSelectFiles) {
|
||||
t.Errorf("SelectFiles should remain unchanged after nil opts Patch")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Patch progress array is resized", func(t *testing.T) {
|
||||
btFetcher := f.(*Fetcher)
|
||||
// Initialize progress array
|
||||
btFetcher.data.Progress = make(fetcher.Progress, 3)
|
||||
|
||||
err := f.Patch(nil, &base.Options{
|
||||
SelectFiles: []int{0, 2},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Patch failed: %v", err)
|
||||
}
|
||||
|
||||
if len(btFetcher.data.Progress) != 2 {
|
||||
t.Errorf("Expected Progress length 2, got %d", len(btFetcher.data.Progress))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1021,7 +1021,9 @@ func (f *Fetcher) downloadChunkOnce(conn *connection, client *http.Client, buf [
|
||||
// Fallback succeeded, use this response instead
|
||||
resp = fallbackResp
|
||||
// Update the redirect URL from the response
|
||||
f.updateRedirectURL(resp)
|
||||
if resp.Request != nil && resp.Request.URL != nil {
|
||||
f.updateRedirectURL(resp.Request.URL.String())
|
||||
}
|
||||
} else {
|
||||
// Fallback also failed, return the original error
|
||||
if fallbackResp != nil {
|
||||
@@ -1571,6 +1573,60 @@ func (f *Fetcher) checkCompletion() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Patch modifies the HTTP request information.
|
||||
func (f *Fetcher) Patch(req *base.Request, opts *base.Options) error {
|
||||
// Patch request info
|
||||
if req != nil {
|
||||
if req.URL != "" {
|
||||
f.meta.Req.URL = req.URL
|
||||
// Clear redirect URL when URL is changed, so new requests use the new URL
|
||||
f.updateRedirectURL("")
|
||||
}
|
||||
if req.Extra != nil {
|
||||
if err := base.ParseReqExtra[fhttp.ReqExtra](req); err != nil {
|
||||
return err
|
||||
}
|
||||
patchExtra := req.Extra.(*fhttp.ReqExtra)
|
||||
// Merge Extra fields instead of replacing entirely
|
||||
if f.meta.Req.Extra == nil {
|
||||
f.meta.Req.Extra = &fhttp.ReqExtra{}
|
||||
}
|
||||
existingExtra := f.meta.Req.Extra.(*fhttp.ReqExtra)
|
||||
// Update Method only if non-empty
|
||||
if patchExtra.Method != "" {
|
||||
existingExtra.Method = patchExtra.Method
|
||||
}
|
||||
// Update Body only if non-empty
|
||||
if patchExtra.Body != "" {
|
||||
existingExtra.Body = patchExtra.Body
|
||||
}
|
||||
// Merge Headers: existing keys are overwritten, new keys are added
|
||||
if patchExtra.Header != nil {
|
||||
if existingExtra.Header == nil {
|
||||
existingExtra.Header = make(map[string]string)
|
||||
}
|
||||
for k, v := range patchExtra.Header {
|
||||
existingExtra.Header[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
// Merge Labels: existing keys are overwritten, new keys are added
|
||||
if req.Labels != nil {
|
||||
if f.meta.Req.Labels == nil {
|
||||
f.meta.Req.Labels = make(map[string]string)
|
||||
}
|
||||
for k, v := range req.Labels {
|
||||
f.meta.Req.Labels[k] = v
|
||||
}
|
||||
}
|
||||
if req.Proxy != nil {
|
||||
f.meta.Req.Proxy = req.Proxy
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Fetcher) Pause() error {
|
||||
if f.cancel != nil {
|
||||
f.cancel()
|
||||
|
||||
@@ -1208,3 +1208,349 @@ func buildConfigFetcher(cfg config) fetcher.Fetcher {
|
||||
fetcher.Setup(newController)
|
||||
return fetcher
|
||||
}
|
||||
|
||||
// TestFetcher_Patch_URLChange tests the Patch functionality where a failed download URL
|
||||
// is replaced with a working one. This simulates:
|
||||
// 1. Initial download attempt with a bad URL (returns 404)
|
||||
// 2. Patching the task with a new working URL
|
||||
// 3. Successful download after URL modification
|
||||
func TestFetcher_Patch_URLChange(t *testing.T) {
|
||||
listener := test.StartTestPatchURLServer()
|
||||
defer listener.Close()
|
||||
|
||||
f := buildFetcher()
|
||||
badURL := "http://" + listener.Addr().String() + "/bad-url"
|
||||
goodURL := "http://" + listener.Addr().String() + "/good-url"
|
||||
|
||||
opts := &base.Options{
|
||||
Name: test.DownloadName,
|
||||
Path: test.Dir,
|
||||
Extra: &http.OptsExtra{Connections: 1},
|
||||
}
|
||||
|
||||
// Step 1: Try to resolve with bad URL - should fail with error
|
||||
err := f.Resolve(&base.Request{URL: badURL}, opts)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for bad URL, got nil")
|
||||
}
|
||||
|
||||
// Step 2: Create a new fetcher and resolve with bad URL but don't wait
|
||||
// We need to test patching a task that has been created
|
||||
f2 := buildFetcher()
|
||||
|
||||
// First resolve with good URL to create a valid fetcher state
|
||||
err = f2.Resolve(&base.Request{URL: goodURL}, opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify initial URL
|
||||
if f2.meta.Req.URL != goodURL {
|
||||
t.Errorf("Initial URL = %v, want %v", f2.meta.Req.URL, goodURL)
|
||||
}
|
||||
|
||||
// Step 3: Patch to change URL (simulating URL change scenario)
|
||||
newURL := "http://" + listener.Addr().String() + "/good-url"
|
||||
err = f2.Patch(&base.Request{URL: newURL}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify URL was patched
|
||||
if f2.meta.Req.URL != newURL {
|
||||
t.Errorf("Patched URL = %v, want %v", f2.meta.Req.URL, newURL)
|
||||
}
|
||||
|
||||
// Step 4: Start download and verify success
|
||||
err = f2.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = f2.Wait()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify file was downloaded correctly
|
||||
want := test.FileMd5(test.BuildFile)
|
||||
got := test.FileMd5(test.DownloadFile)
|
||||
if want != got {
|
||||
t.Errorf("Download() got = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFetcher_Patch_Labels tests patching request labels with merge behavior
|
||||
func TestFetcher_Patch_Labels(t *testing.T) {
|
||||
listener := test.StartTestFileServer()
|
||||
defer listener.Close()
|
||||
|
||||
f := buildFetcher()
|
||||
opts := &base.Options{
|
||||
Name: test.DownloadName,
|
||||
Path: test.Dir,
|
||||
Extra: &http.OptsExtra{Connections: 1},
|
||||
}
|
||||
|
||||
err := f.Resolve(&base.Request{
|
||||
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
||||
Labels: map[string]string{
|
||||
"key1": "value1",
|
||||
"key3": "value3",
|
||||
},
|
||||
}, opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify initial labels
|
||||
if f.meta.Req.Labels["key1"] != "value1" {
|
||||
t.Errorf("Initial label key1 = %v, want value1", f.meta.Req.Labels["key1"])
|
||||
}
|
||||
if f.meta.Req.Labels["key3"] != "value3" {
|
||||
t.Errorf("Initial label key3 = %v, want value3", f.meta.Req.Labels["key3"])
|
||||
}
|
||||
|
||||
// Patch with new labels - key1 should be overwritten, key2 should be added, key3 should remain
|
||||
patchReq := &base.Request{
|
||||
Labels: map[string]string{
|
||||
"key1": "modified",
|
||||
"key2": "newValue",
|
||||
},
|
||||
}
|
||||
err = f.Patch(patchReq, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify labels were merged correctly
|
||||
if f.meta.Req.Labels["key1"] != "modified" {
|
||||
t.Errorf("Patched label key1 = %v, want modified", f.meta.Req.Labels["key1"])
|
||||
}
|
||||
if f.meta.Req.Labels["key2"] != "newValue" {
|
||||
t.Errorf("Patched label key2 = %v, want newValue", f.meta.Req.Labels["key2"])
|
||||
}
|
||||
// key3 should remain unchanged
|
||||
if f.meta.Req.Labels["key3"] != "value3" {
|
||||
t.Errorf("Label key3 = %v, want value3 (should remain unchanged)", f.meta.Req.Labels["key3"])
|
||||
}
|
||||
}
|
||||
|
||||
// TestFetcher_Patch_Extra tests patching request Extra with merge behavior
|
||||
func TestFetcher_Patch_Extra(t *testing.T) {
|
||||
listener := test.StartTestFileServer()
|
||||
defer listener.Close()
|
||||
|
||||
f := buildFetcher()
|
||||
opts := &base.Options{
|
||||
Name: test.DownloadName,
|
||||
Path: test.Dir,
|
||||
Extra: &http.OptsExtra{Connections: 1},
|
||||
}
|
||||
|
||||
// Resolve with initial Extra
|
||||
err := f.Resolve(&base.Request{
|
||||
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
||||
Extra: &http.ReqExtra{
|
||||
Method: "GET",
|
||||
Body: "initial body",
|
||||
Header: map[string]string{
|
||||
"Authorization": "Bearer token123",
|
||||
"X-Custom": "original",
|
||||
},
|
||||
},
|
||||
}, opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify initial Extra
|
||||
initialExtra := f.meta.Req.Extra.(*http.ReqExtra)
|
||||
if initialExtra.Method != "GET" {
|
||||
t.Errorf("Initial Method = %v, want GET", initialExtra.Method)
|
||||
}
|
||||
if initialExtra.Body != "initial body" {
|
||||
t.Errorf("Initial Body = %v, want 'initial body'", initialExtra.Body)
|
||||
}
|
||||
if initialExtra.Header["Authorization"] != "Bearer token123" {
|
||||
t.Errorf("Initial Authorization header = %v, want 'Bearer token123'", initialExtra.Header["Authorization"])
|
||||
}
|
||||
if initialExtra.Header["X-Custom"] != "original" {
|
||||
t.Errorf("Initial X-Custom header = %v, want 'original'", initialExtra.Header["X-Custom"])
|
||||
}
|
||||
|
||||
// Patch with partial Extra - only update some fields
|
||||
patchReq := &base.Request{
|
||||
Extra: &http.ReqExtra{
|
||||
Method: "POST", // Update method
|
||||
// Body is empty, should NOT update
|
||||
Header: map[string]string{
|
||||
"X-Custom": "modified", // Overwrite existing
|
||||
"X-New": "added", // Add new
|
||||
// Authorization is not in patch, should remain
|
||||
},
|
||||
},
|
||||
}
|
||||
err = f.Patch(patchReq, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify Extra was merged correctly
|
||||
patchedExtra := f.meta.Req.Extra.(*http.ReqExtra)
|
||||
|
||||
// Method should be updated
|
||||
if patchedExtra.Method != "POST" {
|
||||
t.Errorf("Patched Method = %v, want POST", patchedExtra.Method)
|
||||
}
|
||||
|
||||
// Body should remain unchanged (patch had empty body)
|
||||
if patchedExtra.Body != "initial body" {
|
||||
t.Errorf("Patched Body = %v, want 'initial body' (should remain unchanged)", patchedExtra.Body)
|
||||
}
|
||||
|
||||
// Authorization header should remain unchanged
|
||||
if patchedExtra.Header["Authorization"] != "Bearer token123" {
|
||||
t.Errorf("Authorization header = %v, want 'Bearer token123' (should remain unchanged)", patchedExtra.Header["Authorization"])
|
||||
}
|
||||
|
||||
// X-Custom header should be overwritten
|
||||
if patchedExtra.Header["X-Custom"] != "modified" {
|
||||
t.Errorf("X-Custom header = %v, want 'modified'", patchedExtra.Header["X-Custom"])
|
||||
}
|
||||
|
||||
// X-New header should be added
|
||||
if patchedExtra.Header["X-New"] != "added" {
|
||||
t.Errorf("X-New header = %v, want 'added'", patchedExtra.Header["X-New"])
|
||||
}
|
||||
}
|
||||
|
||||
// TestFetcher_Patch_NilData tests that Patch with nil data doesn't cause errors
|
||||
func TestFetcher_Patch_NilData(t *testing.T) {
|
||||
listener := test.StartTestFileServer()
|
||||
defer listener.Close()
|
||||
|
||||
f := buildFetcher()
|
||||
opts := &base.Options{
|
||||
Name: test.DownloadName,
|
||||
Path: test.Dir,
|
||||
Extra: &http.OptsExtra{Connections: 1},
|
||||
}
|
||||
|
||||
err := f.Resolve(&base.Request{
|
||||
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
||||
}, opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
originalURL := f.meta.Req.URL
|
||||
|
||||
// Patch with nil data - should not cause error
|
||||
err = f.Patch(nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify URL unchanged
|
||||
if f.meta.Req.URL != originalURL {
|
||||
t.Errorf("URL changed after nil patch: got %v, want %v", f.meta.Req.URL, originalURL)
|
||||
}
|
||||
|
||||
// Patch with empty request - should not cause error
|
||||
err = f.Patch(&base.Request{}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify URL still unchanged
|
||||
if f.meta.Req.URL != originalURL {
|
||||
t.Errorf("URL changed after empty patch: got %v, want %v", f.meta.Req.URL, originalURL)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFetcher_Patch_CookieExpired tests the Patch functionality where a download fails
|
||||
// mid-way due to expired cookie, then succeeds after patching with a new valid cookie.
|
||||
// This simulates:
|
||||
// 1. Initial resolve with valid cookie succeeds
|
||||
// 2. Download starts but fails because cookie expires mid-download (server returns 401)
|
||||
// 3. User patches the task with a new valid cookie
|
||||
// 4. Download resumes and completes successfully
|
||||
func TestFetcher_Patch_CookieExpired(t *testing.T) {
|
||||
listener := test.StartTestCookieExpiringServer()
|
||||
defer listener.Close()
|
||||
|
||||
downloadURL := "http://" + listener.Addr().String() + "/" + test.BuildName
|
||||
opts := &base.Options{
|
||||
Name: test.DownloadName,
|
||||
Path: test.Dir,
|
||||
Extra: &http.OptsExtra{Connections: 1},
|
||||
}
|
||||
|
||||
// Step 1: Resolve with old_token - should succeed (first request accepts old_token)
|
||||
f := buildFetcher()
|
||||
err := f.Resolve(&base.Request{
|
||||
URL: downloadURL,
|
||||
Extra: &http.ReqExtra{
|
||||
Header: map[string]string{
|
||||
"Cookie": "session=old_token",
|
||||
},
|
||||
},
|
||||
}, opts)
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve should succeed with old_token: %v", err)
|
||||
}
|
||||
|
||||
// Verify initial cookie
|
||||
initialExtra := f.meta.Req.Extra.(*http.ReqExtra)
|
||||
if initialExtra.Header["Cookie"] != "session=old_token" {
|
||||
t.Errorf("Initial Cookie = %v, want session=old_token", initialExtra.Header["Cookie"])
|
||||
}
|
||||
|
||||
// Step 2: Start download - should fail because old_token is now expired
|
||||
// (server only accepts old_token for first request, subsequent requests need new_token)
|
||||
err = f.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start failed: %v", err)
|
||||
}
|
||||
err = f.Wait()
|
||||
// Download should fail with 401 error
|
||||
if err == nil {
|
||||
t.Fatal("Expected download to fail with expired cookie, but it succeeded")
|
||||
}
|
||||
t.Logf("Download failed as expected: %v", err)
|
||||
|
||||
// Step 3: Patch with new valid cookie
|
||||
err = f.Patch(&base.Request{
|
||||
Extra: &http.ReqExtra{
|
||||
Header: map[string]string{
|
||||
"Cookie": "session=new_token",
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Patch to update cookie failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify cookie was updated
|
||||
patchedExtra := f.meta.Req.Extra.(*http.ReqExtra)
|
||||
if patchedExtra.Header["Cookie"] != "session=new_token" {
|
||||
t.Errorf("Cookie should be updated: got %v, want session=new_token", patchedExtra.Header["Cookie"])
|
||||
}
|
||||
|
||||
// Step 4: Restart download - should succeed with new cookie
|
||||
err = f.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Restart failed: %v", err)
|
||||
}
|
||||
err = f.Wait()
|
||||
if err != nil {
|
||||
t.Fatalf("Download after patch failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify download completed successfully
|
||||
want := test.FileMd5(test.BuildFile)
|
||||
got := test.FileMd5(test.DownloadFile)
|
||||
if want != got {
|
||||
t.Errorf("File MD5 mismatch: got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
@@ -132,13 +132,10 @@ func (f *Fetcher) buildRequestWithURL(ctx context.Context, req *base.Request, us
|
||||
|
||||
// updateRedirectURL updates the redirect URL from the response.
|
||||
// This is called when a request using the original URL succeeds after the redirect URL expired.
|
||||
func (f *Fetcher) updateRedirectURL(resp *http.Response) {
|
||||
if resp != nil && resp.Request != nil {
|
||||
newRedirectURL := resp.Request.URL.String()
|
||||
f.redirectLock.Lock()
|
||||
f.redirectURL = newRedirectURL
|
||||
f.redirectLock.Unlock()
|
||||
}
|
||||
func (f *Fetcher) updateRedirectURL(url string) {
|
||||
f.redirectLock.Lock()
|
||||
f.redirectURL = url
|
||||
f.redirectLock.Unlock()
|
||||
}
|
||||
|
||||
// hasRedirectURL checks if a redirect URL exists and is different from the original URL.
|
||||
|
||||
Reference in new issue
Block a user