package server import ( "net/http" "net/http/httptest" "testing" ) func TestBlockingAPI(t *testing.T) { e := newAPIEnv(t) lists := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/missing" { http.NotFound(w, r) return } w.Write([]byte("||ads.example^\n0.0.0.0 tracker.example\n")) })) defer lists.Close() b := e.do("POST", "/blocking/lists", e.token, map[string]any{"name": "ads", "url": lists.URL + "/list.txt"}, 201)["object"].(map[string]any) if b["name"] != "ads" || b["state"] != "waiting" { t.Errorf("new list = %v", b) } got := e.do("POST", "/blocking/lists/ads/refresh", e.token, nil, 200) st, _ := got["status"].(map[string]any) if got["state"] != "ok" || st["entries"] != 2.0 || st["records"] != 3.0 { t.Errorf("after refresh = %v", got) } e.do("POST", "/blocking/rules", e.token, map[string]any{"name": "Good.Ads.Example.", "action": "allow"}, 201) r := e.do("POST", "/blocking/rules", e.token, map[string]any{"name": "more.example", "exact": true}, 201)["object"].(map[string]any) if r["name"] != "more.example" || r["action"] != "block" || r["exact"] != true { t.Errorf("new rule = %v", r) } for name, want := range map[string]string{"x.ads.example": "blocked", "a.good.ads.example": "allowed", "more.example": "blocked", "sub.more.example": "none"} { m := e.list("/blocking/check?name="+name, e.token) if len(m) != 1 || m[0]["result"] != want { t.Errorf("check %s = %v, want %s", name, m, want) } } if _, status := e.req("GET", "/blocking/check?name=com", e.token, nil); status != 400 { t.Errorf("check of a TLD = %d", status) } // Staged changes show as pending. cs := itoa(int64(e.do("POST", "/changesets", e.token, map[string]any{"description": "more"}, 201)["id"].(float64))) e.do("POST", "/blocking/rules?changeset="+cs, e.token, map[string]any{"name": "staged.example"}, 202) e.do("PATCH", "/blocking/lists/ads?changeset="+cs, e.token, map[string]any{"disabled": true}, 202) pending := map[string]any{} for _, x := range e.list("/blocking/rules?changeset="+cs, e.token) { pending[x["name"].(string)] = x["pending"] } if pending["staged.example"] != "add" || pending["more.example"] != nil { t.Errorf("pending rules = %v", pending) } if l := e.list("/blocking/lists?changeset="+cs, e.token); len(l) != 1 || l[0]["pending"] != "change" || l[0]["state"] != "disabled" { t.Errorf("pending list = %v", l) } e.do("POST", "/changesets/"+cs+"/discard", e.token, nil, 204) // Delete rules by name; a missing one is a 404 unless missing_ok. e.do("DELETE", "/blocking/rules?name=more.example", e.token, nil, 200) if _, status := e.req("DELETE", "/blocking/rules?name=more.example", e.token, nil); status != 404 { t.Errorf("deleting a missing rule = %d", status) } e.do("DELETE", "/blocking/rules?name=more.example&missing_ok=1", e.token, nil, 200) // Readers see blocking but can't change it. e.do("POST", "/users", e.token, map[string]any{"username": "reader", "password": "reader-password-123", "grant_strings": []string{"blocking:read"}}, 201) var readerID int64 for _, u := range e.list("/users", e.token) { if u["username"] == "reader" { readerID = int64(u["id"].(float64)) } } rt := e.do("POST", "/users/"+itoa(readerID)+"/tokens", e.token, map[string]any{"name": "t"}, 201)["token"].(string) if l := e.list("/blocking/lists", rt); len(l) != 1 { t.Errorf("reader's lists = %v", l) } for _, c := range []struct{ method, path string }{{"POST", "/blocking/rules"}, {"POST", "/blocking/lists/ads/refresh"}, {"DELETE", "/blocking/lists/ads"}} { if _, status := e.req(c.method, c.path, rt, map[string]any{"name": "x.example"}); status != 403 { t.Errorf("reader %s %s = %d", c.method, c.path, status) } } // A list that can't be downloaded shows on the dashboard. e.do("POST", "/blocking/lists", e.token, map[string]any{"name": "gone", "url": lists.URL + "/missing"}, 201) if l := e.do("POST", "/blocking/lists/gone/refresh", e.token, nil, 200); l["state"] != "error" { t.Errorf("failed list = %v", l) } errs, _ := e.do("GET", "/status", e.token, nil, 200)["blocklist_errors"].([]any) if len(errs) != 1 || errs[0].(map[string]any)["name"] != "gone" { t.Errorf("status blocklist_errors = %v", errs) } e.do("DELETE", "/blocking/lists/gone", e.token, nil, 200) e.do("DELETE", "/blocking/lists/ads", e.token, nil, 200) }