From 24e62605b0eb1da9a541725467b618835c58c084 Mon Sep 17 00:00:00 2001 From: Eric Phillips Date: Thu, 16 Apr 2026 22:26:06 -0600 Subject: [PATCH] added timeouts to fetch --- TODO.md | 4 ++- internal/cache/cache.go | 16 +++++++++- internal/cache/cache_test.go | 60 ++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 5f325e2..c73b2f2 100644 --- a/TODO.md +++ b/TODO.md @@ -1,9 +1,11 @@ -- Basic testing for internal/cache +- Basic config Testing +- Add chi for mux - add cli option for config location - Complete testing - Deployment(PKGBUILD, systemd, bootstrap script?, systemd sync timer) - More complete sync(refresh packages on schedule with db, prefetch updates to pkgs we already have) - Build server/tool +- ~Basic testing for internal/cache~ - ~basic file server that fulfills pacman api~ - ~fetch requested files from mirror~ - ~DB sync from mirror~ diff --git a/internal/cache/cache.go b/internal/cache/cache.go index cbd32ed..d108200 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -3,10 +3,12 @@ package cache import ( "fmt" "io" + "net" "net/http" "os" "path/filepath" "sync" + "time" "golang.org/x/sync/singleflight" ) @@ -17,13 +19,25 @@ type Cache struct { mirroredRepos []string sf singleflight.Group //prevents duplicate downloads mu sync.Mutex + client http.Client } func NewCache(cacheRoot string, mirrorURL string) *Cache { + transport := &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 5 * time.Second, + }).DialContext, + ResponseHeaderTimeout: 10 * time.Second, + } + return &Cache{ cacheRoot: cacheRoot, mirrorURL: mirrorURL, mirroredRepos: []string{"core", "extra"}, + client: http.Client{ + Timeout: 15 * time.Second, + Transport: transport, + }, } } @@ -50,7 +64,7 @@ func (c *Cache) fetch(pkgName string) error { outPkg := filepath.Join(c.cacheRoot, pkgName) pkgURL := c.mirrorURL + pkgName - resp, err := http.Get(pkgURL) + resp, err := c.client.Get(pkgURL) if err != nil { return err } diff --git a/internal/cache/cache_test.go b/internal/cache/cache_test.go index 3f97266..fcf353b 100644 --- a/internal/cache/cache_test.go +++ b/internal/cache/cache_test.go @@ -2,12 +2,14 @@ package cache import ( "bytes" + "errors" "fmt" "net/http" "net/http/httptest" "os" "path/filepath" "testing" + "time" ) func newTestServer(t *testing.T, handler http.HandlerFunc) *httptest.Server { @@ -46,3 +48,61 @@ func TestFetchFileExists(t *testing.T) { t.Errorf("expected file to contain %s got %s", expected, data) } } + +func TestFetchNotFound(t *testing.T) { + svr := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + + c := newTestCache(t, svr.URL+"/") + + err := c.Fetch("fakefile") + var upstreamErr *UpstreamError + if !errors.As(err, &upstreamErr) { + t.Fatalf("expected UpstreamError fot %v", err) + } + if upstreamErr.StatusCode != http.StatusNotFound { + t.Errorf("expected 404 got %d", upstreamErr.StatusCode) + } +} + +func TestFetchSrvError(t *testing.T) { + svr := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + + c := newTestCache(t, svr.URL+"/") + + err := c.Fetch("fakefile") + var upstreamErr *UpstreamError + if !errors.As(err, &upstreamErr) { + t.Fatalf("expected UpstreamError fot %v", err) + } + if upstreamErr.StatusCode != http.StatusInternalServerError { + t.Errorf("expected 500 got %d", upstreamErr.StatusCode) + } +} + +func TestFetchSrvDead(t *testing.T) { + svr := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + select { + case <-r.Context().Done(): + return + case <-time.After(60 * time.Second): + fmt.Fprint(w, "too late") + } + })) + defer svr.Close() + + c := newTestCache(t, svr.URL+"/") + + err := c.Fetch("fakefile") + if err == nil { + t.Fatal("expected err got nil") + } + + var upstreamErr *UpstreamError + if errors.As(err, &upstreamErr) { + t.Error("expected network error not UpstreamError") + } +}