Files
goget/internal/protocol/sftp/sftp_graceful_test.go

84 lines
2.3 KiB
Go

//go:build linux || freebsd
// +build linux freebsd
package sftp
import (
"context"
"crypto/rand"
"net/url"
"os"
"path/filepath"
"sync"
"testing"
"codeberg.org/petrbalvin/goget/internal/core"
"codeberg.org/petrbalvin/goget/internal/output"
)
// TestDownloadSingleFileGracefulShutdown verifies that downloadSingleFile
// persists a .goget.meta sidecar when the context is cancelled mid-transfer.
// Without this, a user hitting Ctrl-C during an SFTP single-file download
// would lose all progress and be unable to --resume.
func TestDownloadSingleFileGracefulShutdown(t *testing.T) {
addr, cleanup := startTestServer(t)
defer cleanup()
// Use a large file so the download takes multiple read iterations,
// giving the context cancellation a chance to land between reads.
bigContent := make([]byte, 1<<20) // 1 MiB
if _, err := rand.Read(bigContent); err != nil {
t.Fatalf("rand: %v", err)
}
origContent := testFileContent
testFileContent = bigContent
t.Cleanup(func() { testFileContent = origContent })
u, _ := url.Parse("sftp://user@" + addr + "/testfile")
outDir := t.TempDir()
outputPath := filepath.Join(outDir, "out")
ctx, cancel := context.WithCancel(context.Background())
// Cancel the context on the first progress callback — at that point
// at least one chunk has been written, so total > 0 and the
// .goget.meta sidecar will be persisted.
var once sync.Once
progressCallback := func(current, total int64, speed float64) {
once.Do(cancel)
}
req := &core.DownloadRequest{
URL: u,
Output: outputPath,
Ctx: ctx,
SSHInsecure: true,
ProgressCallback: progressCallback,
}
_, err := download(ctx, req)
if err == nil {
t.Fatal("expected error from cancelled context, got nil")
}
// The output file must exist (partial data was written).
if _, statErr := os.Stat(outputPath); statErr != nil {
t.Fatalf("output file should exist: %v", statErr)
}
// Verify .goget.meta was saved with partial progress.
meta, err := output.LoadResumeMetadata(outputPath)
if err != nil {
t.Fatalf("LoadResumeMetadata: %v", err)
}
if meta == nil {
t.Fatal("expected resume metadata after SIGINT, got nil")
}
if meta.Downloaded <= 0 {
t.Errorf("Downloaded = %d, want > 0", meta.Downloaded)
}
if meta.URL != u.String() {
t.Errorf("URL = %q, want %q", meta.URL, u.String())
}
}