// ingestion/internal/frontmatter/frontmatter_test.go package frontmatter import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" ) func TestLint(t *testing.T) { tests := []struct { name string doc string wantErr bool }{ { name: "no frontmatter block is not an error", doc: "# Just a heading\n\nSome body text.\n", }, { name: "quoted title parses", doc: "---\ntitle: 'Headless dispatch runs: Bash is denied'\n---\n\nbody\n", }, { name: "plain title parses", doc: "---\ntitle: A simple title\nwing: homelab\n---\n\nbody\n", }, { name: "unquoted colon in title does not parse", doc: "---\ntitle: Headless dispatch runs: Bash is denied — use gitea MCP\nwing: homelab\n---\n\nbody\n", wantErr: true, }, { // Known gap, deliberately not fixed here: this parses, but the // item becomes map[string]any{"Headless dispatch": "denied"} // rather than a string. It is a silent type corruption, not a // parse failure, so repairing it would rewrite documents that // currently load — out of scope for a lint about broken YAML. name: "unquoted colon in a list item parses as a mapping, not an error", doc: "---\ntitle: Fine\naliases:\n - Headless dispatch: denied\n---\n\nbody\n", }, { name: "unterminated frontmatter is not treated as frontmatter", doc: "---\ntitle: no closing fence\n\nbody\n", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := Lint([]byte(tt.doc)) if tt.wantErr { require.Error(t, err) return } require.NoError(t, err) }) } } func TestFix(t *testing.T) { tests := []struct { name string doc string wantChanged bool wantKey string wantValue string }{ { name: "quotes an unquoted title containing a colon", doc: "---\ntitle: Headless dispatch runs: Bash is denied — use gitea MCP\nwing: homelab\n---\n\nbody\n", wantChanged: true, wantKey: "title", wantValue: "Headless dispatch runs: Bash is denied — use gitea MCP", }, { // Parses already (see the Lint table), so Fix must leave it alone // rather than rewrite a document that loads. name: "leaves a colon-bearing list item alone because it already parses", doc: "---\ntitle: Fine\naliases:\n - Headless dispatch: denied\n---\n\nbody\n", wantChanged: false, wantKey: "title", wantValue: "Fine", }, { name: "escapes an embedded single quote when quoting", doc: "---\ntitle: Don't do this: it breaks\n---\n\nbody\n", wantChanged: true, wantKey: "title", wantValue: "Don't do this: it breaks", }, { name: "leaves an already-valid document untouched", doc: "---\ntitle: 'Already quoted: fine'\nwing: homelab\n---\n\nbody\n", wantChanged: false, wantKey: "title", wantValue: "Already quoted: fine", }, { name: "leaves a document with no frontmatter untouched", doc: "# Heading\n\nbody: with a colon\n", wantChanged: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, changed, err := Fix([]byte(tt.doc)) require.NoError(t, err) assert.Equal(t, tt.wantChanged, changed) if !tt.wantChanged { assert.Equal(t, tt.doc, string(got), "unchanged docs must be returned byte-identical") } // Whatever came back must parse — that is the whole point. require.NoError(t, Lint(got), "Fix must not return a document that still fails to parse") if tt.wantKey == "" { return } block, ok := block(got) require.True(t, ok) var m map[string]any require.NoError(t, yaml.Unmarshal(block, &m)) assert.Equal(t, tt.wantValue, m[tt.wantKey], "the value must survive quoting unchanged") }) } } func TestFix_PreservesBodyExactly(t *testing.T) { body := "\nThe body has: colons, 'quotes', and\n---\na horizontal rule.\n" doc := "---\ntitle: Broken: title\n---" + body got, changed, err := Fix([]byte(doc)) require.NoError(t, err) require.True(t, changed) assert.True(t, len(got) > 0) _, rest, ok := split([]byte(doc)) require.True(t, ok) _, gotRest, ok := split(got) require.True(t, ok) assert.Equal(t, string(rest), string(gotRest), "the body must not be touched") } func TestFix_RefusesToReturnAnUnparseableResult(t *testing.T) { // A frontmatter block that quoting cannot rescue: the structure itself is // broken, not a scalar. Fix must return the original and an error rather // than a half-repaired document. doc := "---\ntitle: fine\n\tbad: tab-indented mapping\n - stray list item\n---\n\nbody\n" got, changed, err := Fix([]byte(doc)) require.Error(t, err) assert.False(t, changed) assert.Equal(t, doc, string(got), "an unfixable document must come back byte-identical") }