package app import ( "io" "net/http" "os" "strings" "testing" "time" ) // httpResult is a small wrapper so the tests read cleanly. type httpResult struct { status int header http.Header body []byte } var testClient = &http.Client{Timeout: 10 * time.Second} func httpNewRequest(method, url, key string) (*http.Request, error) { req, err := http.NewRequest(method, url, nil) if err != nil { return nil, err } if key != "" { req.Header.Set("X-API-Key", key) } return req, nil } func httpGet(url, key string) (httpResult, error) { req, err := httpNewRequest("GET", url, key) if err != nil { return httpResult{}, err } return httpDo(req) } func httpRequest(method, url, key, body string) (httpResult, error) { var rdr io.Reader if body != "" { rdr = bytesReader(body) } req, err := http.NewRequest(method, url, rdr) if err != nil { return httpResult{}, err } req.Header.Set("X-API-Key", key) req.Header.Set("Content-Type", "application/json") return httpDo(req) } func httpDo(req *http.Request) (httpResult, error) { res, err := testClient.Do(req) if err != nil { return httpResult{}, err } defer res.Body.Close() b, _ := io.ReadAll(res.Body) return httpResult{status: res.StatusCode, header: res.Header, body: b}, nil } func bytesReader(s string) io.Reader { return strings.NewReader(s) } // waitFor polls cond until it holds or the timeout expires. Used where an // action is intentionally asynchronous (blocklist refresh runs in a goroutine). func waitFor(t *testing.T, timeout time.Duration, cond func() bool) { t.Helper() deadline := time.Now().Add(timeout) for time.Now().Before(deadline) { if cond() { return } time.Sleep(25 * time.Millisecond) } t.Fatalf("condition not met within %s", timeout) } func readFileString(t *testing.T, path string) string { t.Helper() b, err := os.ReadFile(path) if err != nil { t.Fatalf("read %s: %v", path, err) } return string(b) } func writeFileString(t *testing.T, path, content string) { t.Helper() if err := os.WriteFile(path, []byte(content), 0o600); err != nil { t.Fatalf("write %s: %v", path, err) } }