package cli_test import ( "os" "path/filepath" "strings" "testing" "git.vakhrushev.me/av/convy/internal/cli" "git.vakhrushev.me/av/convy/internal/manifest" ) // subscribe edits the project manifest the way a person would: by hand, in the // file. That is the whole premise of sync — the manifest is the truth, and the // layout follows it. func subscribe(t *testing.T, root string, topics ...string) { t.Helper() m, err := manifest.LoadProject(root) if err != nil { t.Fatal(err) } c := m.Components["backend"] c.Topics = topics m.Components["backend"] = c if err := m.Save(); err != nil { t.Fatal(err) } } func TestSyncAssemblesWhatIsMissingAndRemovesWhatIsOrphaned(t *testing.T) { suiteRoot := subscribable(t) root := wired(t, suiteRoot, "--component", "backend", "--dir", "docs/conventions", "--lang", "go") run(t, root, "", false, "add", "time") // The manifest is edited by hand: time goes, logging comes. subscribe(t, root, "logging") code, out := run(t, root, "", false, "sync", "--dry-run") if code != cli.OK { t.Fatalf("the dry run returned %d: %s", code, out) } if !strings.Contains(out, "would change") { t.Errorf("the dry run promised nothing:\n%s", out) } if !exists(t, root, "docs/conventions/time.md") { t.Errorf("the dry run removed a file") } if exists(t, root, "docs/conventions/logging.md") { t.Errorf("the dry run assembled a file") } code, out = run(t, root, "", false, "sync") if code != cli.OK { t.Fatalf("sync returned %d: %s", code, out) } if !exists(t, root, "docs/conventions/logging.md") { t.Errorf("the subscribed topic was not assembled:\n%s", out) } if exists(t, root, "docs/conventions/time.md") { t.Errorf("the copy nothing subscribes to stayed:\n%s", out) } // Run again: nothing left to do, and it says so. code, out = run(t, root, "", false, "sync") if code != cli.OK || !strings.Contains(out, "already follows the manifest") { t.Errorf("a second sync found work to do:\n%s", out) } } // Below the marker is the one thing in the directory that exists nowhere else. func TestSyncLeavesAnOrphanCarryingALocalPart(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 += "\nTIME-1 — МЕХАНИЗИРОВАНО: `internal/archrules`.\n" if err := os.WriteFile(name, []byte(body), 0o644); err != nil { t.Fatal(err) } subscribe(t, root) code, out := run(t, root, "", false, "sync") if code == cli.OK { t.Fatalf("an orphan with a local part went unremarked:\n%s", out) } if !strings.Contains(out, "local part") { t.Errorf("the report does not say why the file was left:\n%s", out) } if !exists(t, root, "docs/conventions/time.md") { t.Fatalf("the local part was destroyed:\n%s", out) } } // The manifest is the truth, so a manifest that does not hold together stops // the command before anything is written. func TestSyncValidatesTheManifestBeforeTouchingAnything(t *testing.T) { suiteRoot := subscribable(t) root := wired(t, suiteRoot, "--component", "backend", "--dir", "docs/conventions", "--lang", "go") cases := []struct { name string change func(*manifest.Project) want string }{{ name: "a topic the suite does not declare", change: func(m *manifest.Project) { subscribeTo(m, "billing") }, want: "no such topic", }, { name: "the same topic twice", change: func(m *manifest.Project) { subscribeTo(m, "time", "time") }, want: "twice", }, { name: "two languages in one component", change: func(m *manifest.Project) { c := m.Components["backend"] c.Lang = []string{"go", "javascript"} m.Components["backend"] = c }, want: "declares two languages", }, { name: "a topic no layer of which fits", change: func(m *manifest.Project) { // web-ui lives on the htmx stack only, and this component is on // no stack at all. subscribeTo(m, "web-ui") }, want: "no layer of it fits", }} for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { m, err := manifest.LoadProject(root) if err != nil { t.Fatal(err) } tc.change(m) if err := m.Save(); err != nil { t.Fatal(err) } code, out := run(t, root, "", false, "sync") if code == cli.OK { t.Fatalf("the manifest went through:\n%s", out) } if !strings.Contains(out, tc.want) { t.Errorf("the report does not say %q:\n%s", tc.want, out) } if !strings.Contains(out, "nothing was touched") { t.Errorf("the report does not say it wrote nothing:\n%s", out) } }) } } func subscribeTo(m *manifest.Project, topics ...string) { c := m.Components["backend"] c.Topics = topics m.Components["backend"] = c } func exists(t *testing.T, parts ...string) bool { t.Helper() _, err := os.Stat(filepath.Join(parts...)) return err == nil }