Skip to content
quarry Artifact [f36f63932e]
Login

Artifact [f36f63932e]

Artifact f36f63932e4dce8f3de8d85e7e567f18deb813e71be6bf4a3a3dceefe06f1925:


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)
	}
}