package handlers import ( "fmt" "net/http" "strings" "testing" "github.com/mixeme/selfpost/internal/store" ) // The Outbound log and the page of one message, rendered from the real journal // by the handlers. Their markup is held to the mockups by the guards in // internal/web/view; what is checked here is the behaviour of the pages — the // polled region, the paging, the scoping. // regionOf cuts the polled region (the table and the foot under it) out of a // body: from the table's container to the end of the foot. func regionOf(t *testing.T, body string) string { t.Helper() start := strings.Index(body, `
") return body[start : foot+end+len("
")] } // The fragment answers with the markup the page holds for the same region, so // the first poll changes nothing a person sees: the table, byte for byte, and // the foot, which only gains the attribute that swaps it into place by its id. func TestOutLogFragmentIsTheRegionOfThePage(t *testing.T) { h, _ := serverWithDelivery(t) page := regionOf(t, getBody(t, h.HandleDeliveries, "/outbound/log?domain=bs.example.ru")) fragment := getBody(t, h.HandleDeliveriesRows, "/outbound/log/fragment?domain=bs.example.ru") if !strings.Contains(fragment, `id="out-log-foot" hx-swap-oob="true"`) { t.Errorf("the fragment's foot is not marked to be swapped in by its id:\n%s", fragment) } swapped := strings.Replace(fragment, ` hx-swap-oob="true"`, "", 1) if swapped != page { t.Errorf("the fragment is not the page's polled region\npage:\n%s\nfragment:\n%s", page, fragment) } // The poll asks for the same filters again, and never replaces the filter form. has(t, "region", page, `data-poll`, `aria-live="polite"`, `hx-trigger="load"`, `hx-swap="outerHTML"`, `hx-get="/outbound/log/fragment?domain=bs.example.ru"`) lacks(t, "fragment", fragment, `Older →`, `New rows appear on their own`) lacks(t, "page 1", first, `← Newer`) second := getBody(t, h.HandleDeliveries, "/outbound/log?domain=bs.example.ru&p=2") has(t, "page 2", second, `Page 2 of 2`, `← Newer`, `hx-get="/outbound/log/fragment?domain=bs.example.ru&p=2"`) lacks(t, "page 2", second, `Older →`) // A row's page is opened from the page it is on and leads back to it. has(t, "page 2", second, `?domain=bs.example.ru&p=2">Details`) } // Nothing logged yet is a sentence in the polled region, which goes on polling, // not a table with no rows; the foot still says that rows will appear. func TestOutLogEmpty(t *testing.T) { h, _ := serverWithTwoDomains(t) p := domainAdmin(t, h.store, "no-domains") body := getBodyAs(t, h.HandleDeliveries, "/outbound/log", p) has(t, "empty log", body, `No messages logged yet.`, `id="out-log-rows"`, `data-poll`, `New rows appear on their own`) lacks(t, "empty log", body, `Settings`) } // What a sender wrote is text on the page: a subject or an address is never // markup, in the list, in the fragment or on the message's own page. func TestOutLogPagesEscapeWhatSendersWrote(t *testing.T) { h, _ := serverWithDelivery(t) if err := h.store.InsertQueued(store.SendLogEntry{ QueueID: "EVIL0001", Domain: "bs.example.ru", AppLogin: "Queuer3C", From: `">@bs.example.ru`, To: `@example.ru`, Subject: ` "quoted"`, }); err != nil { t.Fatal(err) } rows, err := h.store.QuerySendLog(store.SendLogFilter{AllDomains: true}, 1, 0) if err != nil || len(rows) != 1 { t.Fatalf("query: %v (%d rows)", err, len(rows)) } for name, body := range map[string]string{ "list": getBody(t, h.HandleDeliveries, "/outbound/log"), "fragment": getBody(t, h.HandleDeliveriesRows, "/outbound/log/fragment"), "message": getBody(t, h.HandleDelivery, "/outbound/log/"+itoa(rows[0].ID)), } { lacks(t, name, body, `