package main
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
"sourcecraft.dev/bigbes/sr-ht-spec/core"
"sourcecraft.dev/bigbes/sr-ht-spec/service"
)
// TestParseDocProposeTakesFlagsAfterThePositionals is the reason
// parseFlagsAnywhere exists: the natural way to type this command puts the
// space and the file first, and Go's flag package would stop there and drop
// every flag that follows.
func TestParseDocProposeTakesFlagsAfterThePositionals(t *testing.T) {
o, err := parseDocPropose([]string{
"~bigbes/rfcs", "rfc-0001.md",
"--title", "RFC-0001",
"--rationale", "because",
"--as", "rfcs/rfc-0001.md",
})
if err != nil {
t.Fatalf("parseDocPropose: %v", err)
}
if o.space.Owner != "bigbes" || o.space.Name != "rfcs" {
t.Errorf("space = %+v want ~bigbes/rfcs", o.space)
}
if len(o.files) != 1 || o.files[0] != "rfc-0001.md" {
t.Errorf("files = %v want [rfc-0001.md]", o.files)
}
if o.title != "RFC-0001" || o.rationale != "because" || o.as != "rfcs/rfc-0001.md" {
t.Errorf("flags after the positionals were dropped: %+v", o)
}
}
func TestParseDocProposeTakesFlagsBetweenPositionals(t *testing.T) {
o, err := parseDocPropose([]string{
"--proposal", "7", "~bigbes/rfcs", "--message", "add two", "a.md", "b.md",
})
if err != nil {
t.Fatalf("parseDocPropose: %v", err)
}
if o.proposalID != 7 || o.message != "add two" {
t.Errorf("flags lost: %+v", o)
}
if len(o.files) != 2 || o.files[0] != "a.md" || o.files[1] != "b.md" {
t.Errorf("files = %v want [a.md b.md] in order", o.files)
}
}
func TestParseDocProposeMintsASessionWhenNoneIsGiven(t *testing.T) {
first, err := parseDocPropose([]string{"~bigbes/rfcs", "a.md"})
if err != nil {
t.Fatalf("parseDocPropose: %v", err)
}
second, err := parseDocPropose([]string{"~bigbes/rfcs", "a.md"})
if err != nil {
t.Fatalf("parseDocPropose: %v", err)
}
if first.session == "" || second.session == "" {
t.Fatal("no session id was minted; the write would be refused for missing provenance")
}
if first.session == second.session {
t.Errorf("two invocations reported the same session %q", first.session)
}
if !strings.HasPrefix(first.session, "cli-") {
t.Errorf("session %q is not marked as coming from this command", first.session)
}
if first.agent != "specsrht-cli" {
t.Errorf("agent = %q want the default specsrht-cli", first.agent)
}
}
func TestParseDocProposeRejectsBadInvocations(t *testing.T) {
tests := map[string][]string{
"no arguments at all": {},
"a space but no file": {"~bigbes/rfcs"},
"a space with no name": {"~bigbes", "a.md"},
"--as with two files": {"~bigbes/rfcs", "a.md", "b.md", "--as", "one.md"},
"a negative proposal id": {"~bigbes/rfcs", "a.md", "--proposal", "-1"},
"a flag that is not ours": {"~bigbes/rfcs", "a.md", "--titel", "typo"},
}
for name, args := range tests {
if _, err := parseDocPropose(args); err == nil {
t.Errorf("parseDocPropose accepted %s: %v", name, args)
}
}
}
func TestLoadWritesDefaultsThePathToTheBaseName(t *testing.T) {
dir := t.TempDir()
local := filepath.Join(dir, "rfc-0001-spec-sr-ht.md")
if err := os.WriteFile(local, []byte("# hello\n"), 0o644); err != nil {
t.Fatal(err)
}
writes, err := loadWrites(docProposeOpts{files: []string{local}})
if err != nil {
t.Fatalf("loadWrites: %v", err)
}
if len(writes) != 1 {
t.Fatalf("got %d writes want 1", len(writes))
}
if writes[0].Path != "rfc-0001-spec-sr-ht.md" {
t.Errorf("path = %q want the base name, not the local directory", writes[0].Path)
}
if string(writes[0].Content) != "# hello\n" {
t.Errorf("content = %q", writes[0].Content)
}
}
// TestLoadWritesRefusesAPathThisCommandInvented guards the one path this layer
// makes up rather than receives: a base name that is not a document path must
// be refused by name, before a proposal row exists to leave behind.
func TestLoadWritesRefusesAPathThisCommandInvented(t *testing.T) {
dir := t.TempDir()
local := filepath.Join(dir, "notes.txt")
if err := os.WriteFile(local, []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
if _, err := loadWrites(docProposeOpts{files: []string{local}}); err == nil {
t.Error("loadWrites accepted a non-.md base name")
}
if _, err := loadWrites(docProposeOpts{files: []string{local}, as: "../escape.md"}); err == nil {
t.Error("loadWrites accepted a path that escapes the space")
}
}
func TestLoadWritesNamesTheFileItCouldNotRead(t *testing.T) {
missing := filepath.Join(t.TempDir(), "gone.md")
_, err := loadWrites(docProposeOpts{files: []string{missing}})
if err == nil {
t.Fatal("loadWrites accepted a missing file")
}
if !strings.Contains(err.Error(), "gone.md") {
t.Errorf("the error does not name the file:\n%v", err)
}
}
func TestPrintProposeResultLeadsToTheURL(t *testing.T) {
var buf bytes.Buffer
res := service.ProposeResult{
Proposal: service.Proposal{
ID: 7,
State: core.StateOpen,
Branch: "proposals/7",
BaseRev: "0123456789abcdef0123456789abcdef01234567",
},
URL: "https://spec.srht.bigb.es/~bigbes/rfcs/p/7",
}
writes := []service.DocumentWrite{{Path: "rfc-0001.md", Content: []byte("abc")}}
if err := printProposeResult(&buf, res, writes); err != nil {
t.Fatalf("printProposeResult: %v", err)
}
out := buf.String()
for _, want := range []string{"proposal 7", "rfc-0001.md (3 bytes)", "proposals/7", res.URL} {
if !strings.Contains(out, want) {
t.Errorf("output does not mention %q:\n%s", want, out)
}
}
if !strings.HasSuffix(strings.TrimRight(out, "\n"), res.URL) {
t.Errorf("the URL is not the last line an operator sees:\n%s", out)
}
}
func TestPrintProposeResultSaysWhenPolicyLandedIt(t *testing.T) {
var buf bytes.Buffer
res := service.ProposeResult{
Proposal: service.Proposal{ID: 8, State: core.StateMerged, Branch: "proposals/8"},
URL: "https://spec.srht.bigb.es/~bigbes/rfcs/p/8",
Merged: true,
}
if err := printProposeResult(&buf, res, nil); err != nil {
t.Fatalf("printProposeResult: %v", err)
}
if !strings.Contains(buf.String(), "auto-merged") {
t.Errorf("a policy-merged write reads as if it is waiting for review:\n%s", buf.String())
}
}
func TestRunDocRejectsAnUnknownSubcommandBeforeTheDatabase(t *testing.T) {
if err := runDoc(nil); err == nil || err.Error() != docUsage {
t.Errorf("empty invocation did not print the usage line: %v", err)
}
err := runDoc([]string{"upload", "~bigbes/rfcs", "a.md"})
if err == nil || !strings.Contains(err.Error(), "propose") {
t.Errorf("unknown subcommand error does not name the real one: %v", err)
}
}