package api
import (
"strings"
"testing"
)
// recordingForum captures modlog lines; every other ForumStore method
// is unreachable from logModeration and stays unimplemented on purpose,
// so a future call from this path fails loudly rather than silently.
type recordingForum struct {
ForumStore
lines [][5]string
fail error
}
func (f *recordingForum) AppendModLog(repo, actor, action, target, reason string) error {
f.lines = append(f.lines, [5]string{repo, actor, action, target, reason})
return f.fail
}
// An attachment decision is recorded on the same public log the forum's
// decisions go to.
//
// Nothing recorded them before. Rejection cleared the hold and removed
// the file; the removal wrote a sourceless A card carrying the actor,
// so the DAG knew who and when, but there was no reason anywhere and no
// surface a person would find (01a3b35f4055).
func TestAnAttachmentDecisionReachesTheModLog(t *testing.T) {
for _, c := range []struct {
action, name, artifact, reason string
}{
{"attach-vouch", "crash.png", "4f9a1b2c3d4e5f60718293a4b5c6d7e8", "looks fine"},
{"attach-reject", "spam.gif", "0011223344556677889900aabbccddee", "unsolicited advertising"},
} {
f := &recordingForum{}
d := Deps{Forum: f}
d.logModeration("alpha", "jva", c.action, c.name, c.artifact, c.reason)
if len(f.lines) != 1 {
t.Fatalf("FAIL: %s wrote %d log lines, want 1", c.action, len(f.lines))
}
got := f.lines[0]
if got[0] != "alpha" || got[1] != "jva" || got[2] != c.action || got[4] != c.reason {
t.Errorf("FAIL: %s logged %v", c.action, got)
}
// The target has to name the FILE. A line naming only a hash is
// a line nobody reads, and the whole point is that a person can
// find out what happened to something they uploaded.
if !strings.Contains(got[3], c.name) {
t.Errorf("FAIL: target %q does not name the file", got[3])
}
if !strings.Contains(got[3], c.artifact[:12]) {
t.Errorf("FAIL: target %q does not identify which artifact", got[3])
}
t.Logf("ok: %s -> %q (%s)", c.action, got[3], got[4])
}
}
// A decision that happened must not be undone because its log line
// could not be written. The forum's own call sites take the same view:
// the alternative is refusing to moderate when the audit trail is down.
func TestAFailedModLogDoesNotBreakTheDecision(t *testing.T) {
f := &recordingForum{fail: errWriteFailed}
d := Deps{Forum: f}
d.logModeration("alpha", "jva", "attach-reject", "spam.gif", "0011223344556677", "spam")
if len(f.lines) != 1 {
t.Fatal("FAIL: the line was not attempted")
}
t.Log("ok: a failing log is reported and swallowed, the decision stands")
}
// No forum plane at all — a forge with moderation unconfigured — must
// not panic the attachment path.
func TestModerationLogIsSilentWithoutAForumPlane(t *testing.T) {
d := Deps{}
d.logModeration("alpha", "jva", "attach-vouch", "x.png", "00112233445566", "")
t.Log("ok: no forum plane, no panic, nothing recorded")
}
var errWriteFailed = errStr("the log is unavailable")
type errStr string
func (e errStr) Error() string { return string(e) }