review: verdigris patina, honest Lane figures, tests that can fail (#102)
Two-axis review found the shipped colour and three weak tests. - --patina was a warm gold at the same hue family as --brass; the spec asked for a cool blue-green so an unhealthy Lane is unmistakably not ember. Now verdigris in both branches, with docs/design-system.md stating why the far side of the wheel is the point. - A Lane pass that returns before computing its figures carries the previous pass's due count and gap forward instead of recording zeroes, and Checked rides beside Due so a stopped Lane is distinguishable from a quiet one. - TestAdminPageWithoutAPollerSaysSo now separates the two causes it conflated, TestOwnerClearsReaderMarks seeds real counters so the clearing assertion can fail, and TestLaneStatus asserts Checked and the carry-forward. - backend/AGENTS.md records the one deliberate owner comparison outside requireOwner and the carry-forward rule.
This commit is contained in:
+94
-29
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -737,16 +738,19 @@ func TestAdminRoutesAreOwnerOnly(t *testing.T) {
|
||||
}
|
||||
|
||||
// The Lane block reports what the poller says, and marks the Lanes that need
|
||||
// attention — a clamped gap, a refusal, or a Site whose pages can only be read
|
||||
// through a sidecar that is not there.
|
||||
// attention — a clamped gap, a refusal, a Site whose pages can only be read
|
||||
// through a sidecar that is not there, and a Lane with Series waiting that its
|
||||
// last pass did not read.
|
||||
func TestAdminPageShowsLaneStatus(t *testing.T) {
|
||||
lanes := fakeLanes{latest.Status{
|
||||
Lanes: []latest.LaneState{
|
||||
{Site: "asura", Due: 12, LastRun: time.Now().Add(-90 * time.Second), Gap: 40 * time.Second},
|
||||
{Site: "kagane", Due: 3, LastRun: time.Now().Add(-time.Minute), Gap: time.Minute, Browser: true},
|
||||
{Site: "demonic", Due: 400, LastRun: time.Now(), Gap: 8 * time.Second, Clamped: true},
|
||||
{Site: "asura", Due: 12, Checked: 12, LastRun: time.Now().Add(-90 * time.Second), Gap: 40 * time.Second},
|
||||
{Site: "kagane", Due: 3, Checked: 3, LastRun: time.Now().Add(-time.Minute), Gap: time.Minute, Browser: true},
|
||||
{Site: "demonic", Due: 400, Checked: 400, LastRun: time.Now(), Gap: 8 * time.Second, Clamped: true},
|
||||
{Site: "comix", Due: 7, LastRun: time.Now(), Gap: time.Minute, Browser: true},
|
||||
},
|
||||
BrowserConfigured: true,
|
||||
BrowserReachable: true,
|
||||
}}
|
||||
router, st, _ := oauthWebTestServer(t, lanes)
|
||||
|
||||
@@ -758,54 +762,115 @@ func TestAdminPageShowsLaneStatus(t *testing.T) {
|
||||
t.Fatalf("GET /ui/admin/lanes status = %d, want 200", rr.Code)
|
||||
}
|
||||
body := rr.Body.String()
|
||||
for _, want := range []string{"asura", "kagane", "12 due", "gap 40s", "ran 1m30s ago", "gap at floor", "no browser", "unreachable"} {
|
||||
for _, want := range []string{"asura", "kagane", "12 due", "12 checked", "gap 40s", "ran 1m30s ago", "gap at floor", "not checking", "reachable"} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("lane status lacks %q:\n%s", want, body)
|
||||
}
|
||||
}
|
||||
// Two Lanes need attention: the clamped one and the one cut off from the
|
||||
// sidecar. The healthy Lane must not be marked.
|
||||
if n := strings.Count(body, `class="attention"`); n != 2 {
|
||||
t.Errorf("attention rows = %d, want 2:\n%s", n, body)
|
||||
// Nothing is refusing and the sidecar is up, so neither mark may appear:
|
||||
// a mark the owner cannot act on is worse than none.
|
||||
for _, unwanted := range []string{"refusing", "no browser"} {
|
||||
if strings.Contains(body, unwanted) {
|
||||
t.Errorf("lane status marks %q on a healthy run:\n%s", unwanted, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No poller and a poller that has not finished a pass are the same to the page:
|
||||
// it says so rather than drawing zeroes that read as a stopped backend.
|
||||
func TestAdminPageWithoutAPollerSaysSo(t *testing.T) {
|
||||
router, st, _ := oauthWebTestServer(t)
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin", nil)
|
||||
// A Lane whose pass never reached a figure must not have that figure drawn as
|
||||
// a zero: a refusing Lane still reports the due count and gap its last real
|
||||
// pass saw, and a Lane that has never reached one omits it entirely.
|
||||
func TestLaneStatusOmitsUnknownGap(t *testing.T) {
|
||||
lanes := fakeLanes{latest.Status{
|
||||
Lanes: []latest.LaneState{{Site: "comix", LastRun: time.Now(), Refusing: true, Browser: true}},
|
||||
BrowserConfigured: true,
|
||||
BrowserReachable: true,
|
||||
}}
|
||||
router, st, _ := oauthWebTestServer(t, lanes)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/ui/admin/lanes", nil)
|
||||
req.AddCookie(sessionCookie(t, st))
|
||||
rr := httptest.NewRecorder()
|
||||
router.ServeHTTP(rr, req)
|
||||
body := rr.Body.String()
|
||||
if !strings.Contains(body, "No data yet") {
|
||||
t.Errorf("admin page with no poller does not say so:\n%s", body)
|
||||
if strings.Contains(body, "gap 0s") {
|
||||
t.Errorf("a Lane with no pace yet states a zero gap:\n%s", body)
|
||||
}
|
||||
if !strings.Contains(body, "not configured") {
|
||||
t.Errorf("admin page does not report the missing browser sidecar:\n%s", body)
|
||||
if !strings.Contains(body, "refusing") {
|
||||
t.Errorf("a refusing Lane is not marked as such:\n%s", body)
|
||||
}
|
||||
}
|
||||
|
||||
// Clearing a Reader's marks answers with the whole roster, so the page cannot
|
||||
// keep showing the record that was just wiped.
|
||||
// No poller and a poller that has not finished a pass both render "no data
|
||||
// yet" rather than zeroes that read as a stopped backend — but they are not
|
||||
// the same fact, so the page must not blame the sidecar when nothing polls.
|
||||
func TestAdminPageWithoutAPollerSaysSo(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
lanes []web.LaneReporter
|
||||
want, unwant string
|
||||
}{
|
||||
{"no poller", nil, "Polling is switched off", "not configured"},
|
||||
{"poller, no pass yet", []web.LaneReporter{fakeLanes{}}, "not configured", "Polling is switched off"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
router, st, _ := oauthWebTestServer(t, tc.lanes...)
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin", nil)
|
||||
req.AddCookie(sessionCookie(t, st))
|
||||
rr := httptest.NewRecorder()
|
||||
router.ServeHTTP(rr, req)
|
||||
body := rr.Body.String()
|
||||
if !strings.Contains(body, "No data yet") {
|
||||
t.Errorf("admin page with no Lane data does not say so:\n%s", body)
|
||||
}
|
||||
if !strings.Contains(body, tc.want) {
|
||||
t.Errorf("admin page lacks %q:\n%s", tc.want, body)
|
||||
}
|
||||
if strings.Contains(body, tc.unwant) {
|
||||
t.Errorf("admin page states %q, which is not what is wrong:\n%s", tc.unwant, body)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A Reader past the disagreement threshold is rendered as blocked, and
|
||||
// clearing their marks both zeroes the counters and lifts the block in the
|
||||
// roster the response carries back.
|
||||
func TestOwnerClearsReaderMarks(t *testing.T) {
|
||||
router, st, _ := oauthWebTestServer(t)
|
||||
theirCookie := signInCookie(t, router)
|
||||
their, _, err := st.GetSession(theirCookie.Value, time.Now())
|
||||
st, dsn := newTestStoreURL(t)
|
||||
router := newRouter(st, testConfig(), nil)
|
||||
cookie := sessionCookie(t, st)
|
||||
// The counters are filled by issue #103; until it lands the only way to
|
||||
// stand a marked Reader up is to write the columns directly.
|
||||
db, err := sql.Open("pgx", dsn)
|
||||
if err != nil {
|
||||
t.Fatalf("GetSession: %v", err)
|
||||
t.Fatalf("open %s: %v", dsn, err)
|
||||
}
|
||||
defer db.Close()
|
||||
if _, err := db.Exec(`UPDATE readers SET sighting_agreements = 4, sighting_disagreements = 3 WHERE id = $1`, st.OwnerID()); err != nil {
|
||||
t.Fatalf("mark reader: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost,
|
||||
"/readers/"+strconv.FormatInt(their.ReaderID, 10)+"/clear-marks", nil)
|
||||
req.AddCookie(sessionCookie(t, st))
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin", nil)
|
||||
req.AddCookie(cookie)
|
||||
rr := httptest.NewRecorder()
|
||||
router.ServeHTTP(rr, req)
|
||||
body := rr.Body.String()
|
||||
if !strings.Contains(body, "4 confirmed / 3 contradicted") {
|
||||
t.Errorf("roster does not report the Reader's marks:\n%s", body)
|
||||
}
|
||||
if !strings.Contains(body, "deferral blocked") {
|
||||
t.Errorf("a Reader at the threshold is not rendered as blocked:\n%s", body)
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodPost,
|
||||
"/readers/"+strconv.FormatInt(st.OwnerID(), 10)+"/clear-marks", nil)
|
||||
req.AddCookie(cookie)
|
||||
rr = httptest.NewRecorder()
|
||||
router.ServeHTTP(rr, req)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("clear marks: status = %d, want 200 (body %s)", rr.Code, rr.Body.String())
|
||||
}
|
||||
body := rr.Body.String()
|
||||
body = rr.Body.String()
|
||||
if !strings.Contains(body, `id="readers"`) {
|
||||
t.Fatalf("clear marks did not re-render the roster:\n%s", body)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user