package api
import (
"bytes"
"net/http"
"net/http/httptest"
"strings"
"testing"
"craftdesign.group/quarry/internal/oltp"
"craftdesign.group/quarry/internal/ops"
)
// switchForum records what forum.moderation asked of the store. Every
// other ForumStore method stays unimplemented on purpose.
type switchForum struct {
ForumStore
set []bool
logs []string
}
func (f *switchForum) SetModeration(_ string, on bool) error { f.set = append(f.set, on); return nil }
func (f *switchForum) AppendModLog(_, _, action, _, _, _ string) error {
f.logs = append(f.logs, action)
return nil
}
func switchRouter(t *testing.T, forum *switchForum) http.Handler {
t.Helper()
r := ops.NewRegistry()
if err := RegisterAll(r, Deps{Version: "v", Repos: testRepos(forumTechIndex(t)), Forum: forum}); err != nil {
t.Fatal(err)
}
h, err := RouterWithCaps(r, "test", func(_, _ string) CapSet { return oltp.CapSet("5") }, nil)
if err != nil {
t.Fatal(err)
}
return h
}
func postSwitch(t *testing.T, h http.Handler, ctype, body string) *httptest.ResponseRecorder {
t.Helper()
req := httptest.NewRequest("POST", "/api/v1/r/test/forum-moderation", bytes.NewReader([]byte(body)))
req.Header.Set("Content-Type", ctype)
w := httptest.NewRecorder()
h.ServeHTTP(w, req)
return w
}
// The moderation switch takes a BOOLEAN, on every door.
//
// The spec published `enabled` on the answer as a boolean and on the
// request as the strings "on"/"off", because the HTML toggle posts the
// button's value and the op took that word verbatim. So a JSON caller
// who did what the answer's shape suggested — `{"enabled": true}` — was
// refused with `cannot unmarshal bool into Go struct field
// moderationSetIn.enabled of type string`: a Go type name in the API's
// mouth, for the one field a reader would never think to send as a
// word. The form is the only caller that speaks the switch's words; it
// translates before it calls, and the op takes the type the fact has.
func TestModerationSwitchTakesABoolean(t *testing.T) {
forum := &switchForum{}
h := switchRouter(t, forum)
for _, c := range []struct {
ctype, body string
want bool
}{
{"application/json", `{"enabled": true}`, true},
{"application/json", `{"enabled": false}`, false},
{"application/x-www-form-urlencoded", "enabled=true", true},
{"application/x-www-form-urlencoded", "enabled=false", false},
} {
before := len(forum.set)
w := postSwitch(t, h, c.ctype, c.body)
if w.Code != http.StatusOK {
t.Fatalf("%s %q: %d %s", c.ctype, c.body, w.Code, w.Body.String())
}
if len(forum.set) != before+1 || forum.set[before] != c.want {
t.Fatalf("%s %q: store saw %v, want one call with %v", c.ctype, c.body, forum.set[before:], c.want)
}
if !strings.Contains(w.Body.String(), `"enabled":`+map[bool]string{true: "true", false: "false"}[c.want]) {
t.Errorf("%s %q: answer %s does not carry the state it set", c.ctype, c.body, w.Body.String())
}
}
if len(forum.logs) != 4 || forum.logs[0] != "enable" || forum.logs[1] != "disable" {
t.Errorf("modlog actions = %v, want enable/disable per call", forum.logs)
}
}