закрыты известные остатки
- документ самоуправления объявляется ключом governance, а не угадывается по «он один и без ключей оси»: конвенция, потерявшая topic, была от него неотличима и тихо теряла все проверки об отъезде к потребителю - проверка путей канона больше не ловит README.md и READING.md — эти два имени значат что-то и на стороне потребителя - lang.Recognize требует совпадения и слов, и номера версии; директории компонентов сверяются на вложенность, а не только на равенство - у обеих проверок появился --json, а convy sync называет ссылки на темы, которых компонент не взял
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -164,3 +165,107 @@ func exists(t *testing.T, parts ...string) bool {
|
||||
_, err := os.Stat(filepath.Join(parts...))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// One directory inside another is the same collision told less plainly, and it
|
||||
// makes every command that walks a component find the copies of the other.
|
||||
func TestNestedComponentDirectoriesAreRefused(t *testing.T) {
|
||||
suiteRoot := subscribable(t)
|
||||
root := wired(t, suiteRoot, "--component", "backend", "--dir", "docs", "--lang", "go")
|
||||
addComponent(t, root, "web", manifest.Component{Dir: "docs/web", Topics: []string{}})
|
||||
|
||||
for _, name := range []string{"sync", "pull", "check"} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
code, out := run(t, root, "", false, name)
|
||||
if code == cli.OK {
|
||||
t.Fatalf("nested directories went through:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "lies inside") {
|
||||
t.Errorf("the refusal does not say what is nested:\n%s", out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A copy may name a rule of a topic the component did not take: naming one
|
||||
// outside the norm is allowed, and the reader loses a pointer rather than a
|
||||
// requirement. It is still the one thing about a copy that cannot be seen
|
||||
// without the suite, so sync says it.
|
||||
func TestSyncNotesAReferenceToAnUnsubscribedTopic(t *testing.T) {
|
||||
suiteRoot := subscribable(t)
|
||||
root := wired(t, suiteRoot, "--component", "backend", "--dir", "docs/conventions", "--lang", "go")
|
||||
run(t, root, "", false, "add", "time")
|
||||
|
||||
name := filepath.Join(root, "docs", "conventions", "time.md")
|
||||
body := read(t, root, "docs/conventions/time.md")
|
||||
body += "\nПодробности — SLOG-1.\n"
|
||||
if err := os.WriteFile(name, []byte(body), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
code, out := run(t, root, "", false, "sync")
|
||||
if code != cli.OK {
|
||||
t.Fatalf("sync returned %d: %s", code, out)
|
||||
}
|
||||
if !strings.Contains(out, "SLOG-1") || !strings.Contains(out, "logging") {
|
||||
t.Errorf("the note does not name the reference and its topic:\n%s", out)
|
||||
}
|
||||
|
||||
// Subscribed, it is no longer dangling.
|
||||
run(t, root, "", false, "add", "logging")
|
||||
_, out = run(t, root, "", false, "sync")
|
||||
if strings.Contains(out, "not taken by this component") {
|
||||
t.Errorf("the note stayed after the topic was taken:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// The findings a person reads and the findings a machine reads are the same
|
||||
// findings, in the same order.
|
||||
func TestCheckWritesJSONForACallerThatIsNotAPerson(t *testing.T) {
|
||||
suiteRoot := subscribable(t)
|
||||
root := wired(t, suiteRoot, "--component", "backend", "--dir", "docs/conventions", "--lang", "go")
|
||||
run(t, root, "", false, "add", "time")
|
||||
|
||||
code, out := run(t, root, "", false, "check", "--json")
|
||||
if code != cli.OK {
|
||||
t.Fatalf("convy check --json returned %d: %s", code, out)
|
||||
}
|
||||
var clean struct {
|
||||
Findings []map[string]any `json:"findings"`
|
||||
Errors int `json:"errors"`
|
||||
Warnings int `json:"warnings"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(out), &clean); err != nil {
|
||||
t.Fatalf("the output is not JSON: %v\n%s", err, out)
|
||||
}
|
||||
if len(clean.Findings) != 0 || clean.Errors != 0 {
|
||||
t.Errorf("a sound project reported findings: %s", out)
|
||||
}
|
||||
|
||||
// Now break it and read the finding back.
|
||||
name := filepath.Join(root, "docs", "conventions", "time.md")
|
||||
body := read(t, root, "docs/conventions/time.md")
|
||||
body = strings.Replace(body, "<!-- conv:local -->", "", 1)
|
||||
if err := os.WriteFile(name, []byte(body), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
code, out = run(t, root, "", false, "check", "--json")
|
||||
if code == cli.OK {
|
||||
t.Fatalf("the broken copy passed:\n%s", out)
|
||||
}
|
||||
if err := json.Unmarshal([]byte(out), &clean); err != nil {
|
||||
t.Fatalf("the output is not JSON: %v\n%s", err, out)
|
||||
}
|
||||
if clean.Errors != 1 || len(clean.Findings) != 1 {
|
||||
t.Fatalf("expected one error, got %s", out)
|
||||
}
|
||||
f := clean.Findings[0]
|
||||
for key, want := range map[string]any{"severity": "error", "family": "spread"} {
|
||||
if f[key] != want {
|
||||
t.Errorf("%s is %v, expected %v", key, f[key], want)
|
||||
}
|
||||
}
|
||||
if f["path"] != "docs/conventions/time.md" {
|
||||
t.Errorf("path is %v", f["path"])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user