package api
import (
"context"
"errors"
"testing"
"craftdesign.group/quarry/internal/ops"
)
// memRedactions is the redaction store in memory.
type memRedactions map[string]string
func (m memRedactions) Redact(repo, artifact, reason, actor string) error {
m[artifact] = reason
return nil
}
func (m memRedactions) Unredact(repo, artifact string) error { delete(m, artifact); return nil }
func (m memRedactions) Redacted(repo string) (map[string]string, error) {
return map[string]string(m), nil
}
// A redacted version keeps its row in the history but its text reaches
// only moderators; the current version cannot be redacted; every
// redaction is logged with its reason and the author is told.
func TestRedactingAWikiVersion(t *testing.T) {
red := memRedactions{}
ff := &recordingForum{}
r := ops.NewRegistry()
if err := RegisterAll(r, Deps{Version: "v", Repos: testRepos(wikiIndex(t)), Redactions: red, Forum: ff}); err != nil {
t.Fatal(err)
}
mod := ops.WithCaps(ops.WithUser(context.Background(), "mo"), letterCaps("5"))
reader := ops.WithCaps(ops.WithUser(context.Background(), "jan"), letterCaps("j"))
// aaa3 is Home's current version, aaa2 the one before it.
if _, err := r.Invoke(mod, "wiki.redact", redactIn{Repo: "test", Name: "Home", Rev: "aaa3", Reason: "x"}); !errors.Is(err, ops.ErrInvalid) {
t.Fatalf("FAIL: redacting the current version: err = %v, want ErrInvalid", err)
}
if _, err := r.Invoke(mod, "wiki.redact", redactIn{Repo: "test", Name: "Home", Rev: "aaa2"}); !errors.Is(err, ops.ErrInvalid) {
t.Fatalf("FAIL: redacting with no reason: err = %v, want ErrInvalid", err)
}
if _, err := r.Invoke(mod, "wiki.redact", redactIn{Repo: "test", Name: "Home", Rev: "aaa2", Reason: "personal data"}); err != nil {
t.Fatal(err)
}
t.Log("ok: current version refused, reason required, past version redacted")
got, err := r.Invoke(reader, "wiki.get", wikiGetIn{Repo: "test", Name: "Home", Rev: "aaa2"})
if err != nil {
t.Fatal(err)
}
if g := got.(wikiGetOut); g.Source != "" || g.HTML != "" || g.Redacted != "personal data" {
t.Fatalf("FAIL: a reader got the redacted version: %+v", g)
}
got, err = r.Invoke(mod, "wiki.get", wikiGetIn{Repo: "test", Name: "Home", Rev: "aaa2"})
if err != nil {
t.Fatal(err)
}
if g := got.(wikiGetOut); g.Source == "" || g.Redacted != "personal data" {
t.Fatalf("FAIL: a moderator lost the redacted text: %+v", g)
}
t.Log("ok: withheld from a reader, shown to a moderator with the reason")
h, err := r.Invoke(reader, "wiki.history", wikiHistoryIn{Repo: "test", Name: "Home"})
if err != nil {
t.Fatal(err)
}
if hs := h.(wikiHistoryOut); hs.Count != 2 || hs.Redacted["aaa2"] != "personal data" || len(hs.Redacted) != 1 {
t.Fatalf("FAIL: history = %+v", hs)
}
if len(ff.lines) != 1 || ff.lines[0][2] != "redact" || ff.lines[0][4] != "personal data" || ff.lines[0][5] != "dan" {
t.Fatalf("FAIL: log = %q", ff.lines)
}
t.Log("ok: history keeps the row and marks it; the log names the author")
if _, err := r.Invoke(mod, "wiki.redact", redactIn{Repo: "test", Name: "Home", Rev: "aaa2", Undo: true}); err != nil {
t.Fatal(err)
}
got, _ = r.Invoke(reader, "wiki.get", wikiGetIn{Repo: "test", Name: "Home", Rev: "aaa2"})
if g := got.(wikiGetOut); g.Source == "" || g.Redacted != "" {
t.Fatalf("FAIL: unredact did not restore the text: %+v", g)
}
if len(ff.lines) != 2 || ff.lines[1][2] != "unredact" {
t.Fatalf("FAIL: log = %q", ff.lines)
}
t.Log("ok: unredact restores the text and is logged")
}