package scaffold import ( "os" "path/filepath" "strings" "testing" "khosra/internal/content" ) func TestNewWritesADirectoryBundleThatScansAsADraft(t *testing.T) { dir := t.TempDir() written, err := New(dir, "posts/hello-world", "", "") if err != nil { t.Fatal(err) } // A directory bundle, because only that shape can own local files (content-model.md). if written != "content/posts/hello-world/index.en.md" { t.Errorf("wrote %q, want a directory bundle", written) } data, err := os.ReadFile(filepath.Join(dir, written)) if err != nil { t.Fatal(err) } // Parsed by the engine's own parser, not by eye: the scaffold has to be something khosra can read. b, err := content.Parse(strings.TrimPrefix(written, "content/"), data) if err != nil { t.Fatalf("the engine cannot parse its own scaffold: %v\n%s", err, data) } if b.Title != "Hello world" { t.Errorf("title = %q, want one derived from the slug", b.Title) } if !b.Draft { t.Error("a scaffold must be a draft: a tool that publishes when it runs publishes by accident") } if b.Date.IsZero() { t.Error("no date was written") } if b.Key != "posts/hello-world" { t.Errorf("key = %q", b.Key) } } func TestNewHonoursLanguageAndTitle(t *testing.T) { dir := t.TempDir() written, err := New(dir, "pages/about", "bn", "পরিচিতি") if err != nil { t.Fatal(err) } if written != "content/pages/about/index.bn.md" { t.Errorf("wrote %q, want the Bengali variant", written) } data, _ := os.ReadFile(filepath.Join(dir, written)) if !strings.Contains(string(data), "title: পরিচিতি") { t.Errorf("the given title should be used verbatim:\n%s", data) } } func TestNewNeverOverwritesAndNeverEscapes(t *testing.T) { dir := t.TempDir() if _, err := New(dir, "posts/twice", "", ""); err != nil { t.Fatal(err) } if _, err := New(dir, "posts/twice", "", ""); err == nil { t.Error("a second run must refuse rather than replace someone's writing") } // os.Root refuses an escape, and the key is normalised and trimmed before it is used at all (ADR-0031). for _, key := range []string{"../outside", "posts/../../outside", "", "/"} { if _, err := New(dir, key, "", ""); err == nil { t.Errorf("New(%q) should have failed", key) } } if _, err := os.Stat(filepath.Join(filepath.Dir(dir), "outside")); err == nil { t.Fatal("something was written outside the site root") } // The weaker check above is not enough: `../outside` does not escape the root, it lands *inside* it and // outside content/, because path.Join collapses `..` before os.Root ever sees the name. So assert what // should be true — content/ is the only thing this ever creates. entries, err := os.ReadDir(dir) if err != nil { t.Fatal(err) } for _, e := range entries { if e.Name() != "content" { t.Errorf("created %q at the site root; only content/ should ever appear", e.Name()) } } }