@@ -16,11 +16,42 @@ import (
1616 "strings"
1717 "sync/atomic"
1818 "testing"
19+ "time"
1920
2021 "github.com/stretchr/testify/assert"
2122 "github.com/stretchr/testify/require"
2223)
2324
25+ type redirectTransport struct {
26+ srvURL string
27+ base http.RoundTripper
28+ }
29+
30+ func (t redirectTransport ) RoundTrip (req * http.Request ) (* http.Response , error ) {
31+ clone := req .Clone (req .Context ())
32+ clone .URL .Scheme = "http"
33+ clone .URL .Host = strings .TrimPrefix (strings .TrimPrefix (t .srvURL , "https://" ), "http://" )
34+ if t .base == nil {
35+ t .base = http .DefaultTransport
36+ }
37+ return t .base .RoundTrip (clone )
38+ }
39+
40+ func withTestReleaseServer (t * testing.T , srv * httptest.Server ) {
41+ t .Helper ()
42+ origClient := HTTPClient
43+ origBaseURL := ReleaseBaseURL
44+ HTTPClient = & http.Client {
45+ Transport : redirectTransport {srvURL : srv .URL },
46+ Timeout : 120 * time .Second ,
47+ }
48+ ReleaseBaseURL = srv .URL
49+ t .Cleanup (func () {
50+ HTTPClient = origClient
51+ ReleaseBaseURL = origBaseURL
52+ })
53+ }
54+
2455func TestExtractFullsendFromTarGz_PathTraversal (t * testing.T ) {
2556 var buf bytes.Buffer
2657 gw := gzip .NewWriter (& buf )
@@ -412,6 +443,132 @@ func TestResolveForRun_PrefersReleaseBeforeCrossCompile(t *testing.T) {
412443 assert .Equal (t , SourceReleaseDownload , result .Source )
413444}
414445
446+ func TestDownloadRelease_ExceedsMaxSize (t * testing.T ) {
447+ origLimit := maxDownloadSize
448+ maxDownloadSize = 512
449+ t .Cleanup (func () { maxDownloadSize = origLimit })
450+
451+ content := bytes .Repeat ([]byte ("x" ), 2000 )
452+
453+ var tarBuf bytes.Buffer
454+ gw , err := gzip .NewWriterLevel (& tarBuf , gzip .NoCompression )
455+ require .NoError (t , err )
456+ tw := tar .NewWriter (gw )
457+ require .NoError (t , tw .WriteHeader (& tar.Header {
458+ Name : "fullsend" ,
459+ Size : int64 (len (content )),
460+ Mode : 0o755 ,
461+ Typeflag : tar .TypeReg ,
462+ }))
463+ _ , err = tw .Write (content )
464+ require .NoError (t , err )
465+ require .NoError (t , tw .Close ())
466+ require .NoError (t , gw .Close ())
467+
468+ tarBytes := tarBuf .Bytes ()
469+ h := sha256 .Sum256 (tarBytes )
470+ checksumBody := fmt .Sprintf ("%s fullsend_1.0.0_linux_amd64.tar.gz\n " , hex .EncodeToString (h [:]))
471+
472+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
473+ if r .URL .Path == "/v1.0.0/checksums.txt" {
474+ fmt .Fprint (w , checksumBody )
475+ } else if r .URL .Path == "/v1.0.0/fullsend_1.0.0_linux_amd64.tar.gz" {
476+ w .Write (tarBytes )
477+ } else {
478+ http .NotFound (w , r )
479+ }
480+ }))
481+ defer srv .Close ()
482+ withTestReleaseServer (t , srv )
483+
484+ destPath := filepath .Join (t .TempDir (), "fullsend" )
485+ err = DownloadRelease ("1.0.0" , "amd64" , destPath )
486+ require .Error (t , err )
487+ assert .Contains (t , err .Error (), "exceeds maximum size" )
488+ }
489+
490+ func TestResolveForRun_CrossCompileFallback (t * testing.T ) {
491+ if testing .Short () {
492+ t .Skip ("skipping cross-compilation in short mode" )
493+ }
494+
495+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
496+ http .NotFound (w , r )
497+ }))
498+ defer srv .Close ()
499+ withTestReleaseServer (t , srv )
500+
501+ result , err := ResolveForRun ("0.4.0" , "amd64" )
502+ require .NoError (t , err )
503+ t .Cleanup (func () { os .RemoveAll (result .TmpDir ) })
504+ assert .Equal (t , SourceCheckoutBuild , result .Source )
505+ }
506+
507+ func TestResolveForRun_LatestReleaseFallback (t * testing.T ) {
508+ var tarBuf bytes.Buffer
509+ gw := gzip .NewWriter (& tarBuf )
510+ tw := tar .NewWriter (gw )
511+ content := []byte ("latest release binary" )
512+ require .NoError (t , tw .WriteHeader (& tar.Header {
513+ Name : "fullsend" ,
514+ Size : int64 (len (content )),
515+ Mode : 0o755 ,
516+ Typeflag : tar .TypeReg ,
517+ }))
518+ _ , err := tw .Write (content )
519+ require .NoError (t , err )
520+ require .NoError (t , tw .Close ())
521+ require .NoError (t , gw .Close ())
522+
523+ tarBytes := tarBuf .Bytes ()
524+ h := sha256 .Sum256 (tarBytes )
525+ correctHash := hex .EncodeToString (h [:])
526+ checksumBody := fmt .Sprintf ("%s fullsend_9.9.9_linux_amd64.tar.gz\n " , correctHash )
527+
528+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
529+ if r .URL .Path == "/repos/fullsend-ai/fullsend/releases/latest" {
530+ fmt .Fprint (w , `{"tag_name":"v9.9.9"}` )
531+ } else if r .URL .Path == "/v9.9.9/checksums.txt" {
532+ fmt .Fprint (w , checksumBody )
533+ } else if r .URL .Path == "/v9.9.9/fullsend_9.9.9_linux_amd64.tar.gz" {
534+ w .Write (tarBytes )
535+ } else {
536+ http .NotFound (w , r )
537+ }
538+ }))
539+ defer srv .Close ()
540+ withTestReleaseServer (t , srv )
541+
542+ origDir , err := os .Getwd ()
543+ require .NoError (t , err )
544+ tmpDir := t .TempDir ()
545+ require .NoError (t , os .Chdir (tmpDir ))
546+ t .Cleanup (func () { _ = os .Chdir (origDir ) })
547+
548+ result , err := ResolveForRun ("dev" , "amd64" )
549+ require .NoError (t , err )
550+ t .Cleanup (func () { os .RemoveAll (result .TmpDir ) })
551+ assert .Equal (t , SourceReleaseDownload , result .Source )
552+ }
553+
554+ func TestResolveForRun_AllStrategiesFail (t * testing.T ) {
555+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
556+ http .NotFound (w , r )
557+ }))
558+ defer srv .Close ()
559+ withTestReleaseServer (t , srv )
560+
561+ origDir , err := os .Getwd ()
562+ require .NoError (t , err )
563+ tmpDir := t .TempDir ()
564+ require .NoError (t , os .Chdir (tmpDir ))
565+ t .Cleanup (func () { _ = os .Chdir (origDir ) })
566+
567+ _ , err = ResolveForRun ("dev" , "amd64" )
568+ require .Error (t , err )
569+ assert .Contains (t , err .Error (), "all strategies failed" )
570+ }
571+
415572func TestResolveExplicit_ValidatesELF (t * testing.T ) {
416573 tmp := filepath .Join (t .TempDir (), "not-elf" )
417574 require .NoError (t , os .WriteFile (tmp , []byte ("not binary" ), 0o644 ))
0 commit comments