//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()) } }