Skip to content
quarry Artifact [ca48f06c2d]
Login

Artifact [ca48f06c2d]

Artifact ca48f06c2d49ddf1b83212fe8269a8d5d56db0f18c59507006bd3f3606c96412:


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