aboutsummaryrefslogtreecommitdiff
path: root/gui/internal/model/mainconf_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'gui/internal/model/mainconf_test.go')
-rw-r--r--gui/internal/model/mainconf_test.go169
1 files changed, 169 insertions, 0 deletions
diff --git a/gui/internal/model/mainconf_test.go b/gui/internal/model/mainconf_test.go
new file mode 100644
index 0000000..bae739a
--- /dev/null
+++ b/gui/internal/model/mainconf_test.go
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package model
+
+import (
+ "errors"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "krino/internal/engine"
+)
+
+// mainConf opens krino.conf over a sandbox whose main file holds text.
+func mainConf(t *testing.T, main string) (*MainConf, *engine.Engine, string) {
+ t.Helper()
+ e, h := sandboxDir(t, "(path \"~/dl\")\n(rule \"all\" (move \"Out\"))\n",
+ map[string]string{"a.pdf": "one"})
+ if main != "" {
+ if err := os.WriteFile(e.MainFile, []byte(main), 0o644); err != nil {
+ t.Fatal(err)
+ }
+ reloaded, diags := engine.Load(e.MainFile)
+ if len(diags) > 0 {
+ t.Fatalf("the fixture does not load: %v", diags)
+ }
+ e = reloaded
+ }
+ m, err := OpenMain(e)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return m, e, h
+}
+
+// TestMainSettingsReadAndWrite: a default is read as written, changed in
+// place, and taken out again, with everything else in the file untouched.
+func TestMainSettingsReadAndWrite(t *testing.T) {
+ main := ";; the main file\n(include \"dl\")\n\n(defaults\n (min-age 2m) ; leave fresh files\n (on-conflict suffix))\n"
+ m, _, _ := mainConf(t, main)
+
+ if args, set, err := m.Setting("min-age"); err != nil || !set || args != "2m" {
+ t.Errorf("min-age = %q, %v, %v", args, set, err)
+ }
+ if _, set, _ := m.Setting("fold"); set {
+ t.Error("fold reads as set")
+ }
+ if err := m.SetSetting("min-age", "1d"); err != nil {
+ t.Fatal(err)
+ }
+ if !strings.Contains(m.Text, "(min-age 1d)") || strings.Contains(m.Text, "(min-age 2m)") {
+ t.Errorf("min-age not rewritten:\n%s", m.Text)
+ }
+ for _, keep := range []string{";; the main file", "(include \"dl\")", "; leave fresh files",
+ "(on-conflict suffix)"} {
+ if !strings.Contains(m.Text, keep) {
+ t.Errorf("writing one setting lost %q:\n%s", keep, m.Text)
+ }
+ }
+ // A setting the file does not have goes inside (defaults ...).
+ if err := m.SetSetting("fold", "no"); err != nil {
+ t.Fatal(err)
+ }
+ if !strings.Contains(m.Text, "(fold no)") {
+ t.Errorf("fold not written:\n%s", m.Text)
+ }
+ if strings.Index(m.Text, "(fold no)") < strings.Index(m.Text, "(defaults") {
+ t.Errorf("fold landed outside the defaults:\n%s", m.Text)
+ }
+ if diags := m.Check(); len(diags) > 0 {
+ t.Fatalf("the file no longer loads: %v", diags)
+ }
+ // And out again.
+ if err := m.SetSetting("min-age", ""); err != nil {
+ t.Fatal(err)
+ }
+ if strings.Contains(m.Text, "min-age") {
+ t.Errorf("min-age still there:\n%s", m.Text)
+ }
+ if diags := m.Check(); len(diags) > 0 {
+ t.Errorf("the file no longer loads: %v", diags)
+ }
+}
+
+// TestMainSettingsWithoutADefaultsForm: a file with no (defaults ...) gets
+// one, and it loads.
+func TestMainSettingsWithoutADefaultsForm(t *testing.T) {
+ m, _, _ := mainConf(t, "(include \"dl\")\n")
+ if err := m.SetSetting("recursive", "yes"); err != nil {
+ t.Fatal(err)
+ }
+ if !strings.Contains(m.Text, "(defaults") || !strings.Contains(m.Text, "(recursive yes)") {
+ t.Errorf("no defaults form was written:\n%s", m.Text)
+ }
+ if diags := m.Check(); len(diags) > 0 {
+ t.Errorf("the file no longer loads: %v", diags)
+ }
+}
+
+// TestMainLogIsTopLevel: the log path is not a default; it is written at
+// the top level, where krino.conf(5) puts it.
+func TestMainLogIsTopLevel(t *testing.T) {
+ m, _, h := mainConf(t, "(include \"dl\")\n")
+ if err := m.SetSetting("log", "\"~/tmp/krino.log\""); err != nil {
+ t.Fatal(err)
+ }
+ if strings.Contains(m.Text, "(defaults") {
+ t.Errorf("the log went into the defaults:\n%s", m.Text)
+ }
+ if diags := m.Check(); len(diags) > 0 {
+ t.Fatalf("the file no longer loads: %v", diags)
+ }
+ if err := m.Save(); err != nil {
+ t.Fatal(err)
+ }
+ e, diags := engine.Load(filepath.Join(h, ".config", "krino", "krino.conf"))
+ if len(diags) > 0 {
+ t.Fatal(diags)
+ }
+ if !strings.HasSuffix(e.Config.LogFile(), "tmp/krino.log") {
+ t.Errorf("the saved log path is not in effect: %s", e.Config.LogFile())
+ }
+}
+
+// TestMainSaveGuards: krino.conf is saved by the same rules as a directory
+// file - never broken, never over someone else's edit, and the previous
+// text is kept.
+func TestMainSaveGuards(t *testing.T) {
+ main := "(include \"dl\")\n"
+ m, _, _ := mainConf(t, main)
+
+ m.Text = "(include \"dl\")\n(defaults (min-age nonsense))\n"
+ if err := m.Save(); err == nil {
+ t.Error("a file that will not load was saved")
+ }
+ if on, _ := os.ReadFile(m.File); string(on) != main {
+ t.Errorf("the refused save wrote anyway: %q", on)
+ }
+
+ m.Text = "(include \"dl\")\n(defaults (min-age 1d))\n"
+ if err := m.Save(); err != nil {
+ t.Fatal(err)
+ }
+ if on, _ := os.ReadFile(m.File + ".bak"); string(on) != main {
+ t.Errorf("backup = %q, want the previous text", on)
+ }
+
+ // Someone else edits it, and the next save refuses.
+ if err := os.WriteFile(m.File, []byte("(include \"dl\")\n;; theirs\n"), 0o644); err != nil {
+ t.Fatal(err)
+ }
+ m.Text = "(include \"dl\")\n;; mine\n"
+ if err := m.Save(); !errors.Is(err, ErrChangedOnDisk) {
+ t.Errorf("Save = %v, want ErrChangedOnDisk", err)
+ }
+}
+
+// TestMainSettingRefusesAnUnknownHead: only the settings krino.conf(5)
+// documents can be written this way.
+func TestMainSettingRefusesAnUnknownHead(t *testing.T) {
+ m, _, _ := mainConf(t, "(include \"dl\")\n")
+ if err := m.SetSetting("nonsense", "1"); err == nil {
+ t.Error("an unknown setting was accepted")
+ }
+ if _, _, err := m.Setting("nonsense"); err == nil {
+ t.Error("an unknown setting was read")
+ }
+}