jarl block pause|resume (and the Blocking page) lets every name through for a while; jarld resumes on time. The lists stay loaded: the rules zone just passes everything. Statistics gain the clients with the most blocked queries and blocked queries on the queries chart. The view choice in the blocking forms is a dropdown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
112 lines
5.0 KiB
Go
112 lines
5.0 KiB
Go
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)
|
|
}
|
|
}
|
|
// Pausing.
|
|
if _, status := e.req("POST", "/blocking/pause", e.token, map[string]any{"minutes": 0}); status != 400 {
|
|
t.Errorf("a 0-minute pause = %d", status)
|
|
}
|
|
e.do("POST", "/blocking/pause", e.token, map[string]any{"minutes": 30}, 201)
|
|
if st := e.do("GET", "/blocking/status", e.token, nil, 200); st["paused_until"] == nil {
|
|
t.Errorf("status while paused = %v", st)
|
|
}
|
|
if st := e.do("GET", "/status", e.token, nil, 200); st["blocking_paused_until"] == nil {
|
|
t.Errorf("dashboard while paused = %v", st["blocking_paused_until"])
|
|
}
|
|
e.do("POST", "/blocking/resume", e.token, nil, 201)
|
|
if st := e.do("GET", "/blocking/status", e.token, nil, 200); st["paused_until"] != nil {
|
|
t.Errorf("status after resuming = %v", st)
|
|
}
|
|
|
|
// 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)
|
|
}
|