120 lines
4.6 KiB
Go
120 lines
4.6 KiB
Go
package seed
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// decode_test.go: the UNKNOWN-KEY contract of the two decision files (backlog row 212).
|
|
//
|
|
// The defect these pin is silent substitution, not a parse failure: yaml.v3 DROPS a key the struct does
|
|
// not have, so `gendr:` for `gender:` was thrown away BEFORE any validation ran — and `seed-lint` could
|
|
// not see it either, because it validates VALUES and the key never reached it. The same class pack-16
|
|
// bought strictness against for book.yaml.
|
|
|
|
func TestSeedDecodeRefusesAnUnknownKey(t *testing.T) {
|
|
_, err := DecodeFile([]byte("terms:\n - src: 方源\n dst: Фан Юань\n gendr: male\n"))
|
|
if err == nil {
|
|
t.Fatal("a misspelled term key must be a loud error, not a dropped field")
|
|
}
|
|
if !strings.Contains(err.Error(), "gendr") {
|
|
t.Errorf("the error must name the key it did not recognise, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSeedDecodeRefusesAnUnknownTopLevelKey(t *testing.T) {
|
|
if _, err := DecodeFile([]byte("termz:\n - src: 方源\n")); err == nil {
|
|
t.Fatal("a misspelled SECTION name silently yields an empty seed — it must be loud")
|
|
}
|
|
}
|
|
|
|
func TestRejectsDecodeRefusesAnUnknownKey(t *testing.T) {
|
|
// `rejcts:` used to read as an empty reject list, silently un-declining every term the owner
|
|
// declined — they all re-enter the signature map and the auto-bank as if the review never happened.
|
|
if _, err := DecodeRejects([]byte("rejcts:\n - src: 方源\n")); err == nil {
|
|
t.Fatal("a misspelled reject section must be a loud error")
|
|
}
|
|
if _, err := DecodeRejects([]byte("rejects:\n - src: 方源\n reasn: no\n")); err == nil {
|
|
t.Fatal("a misspelled reject field must be a loud error")
|
|
}
|
|
}
|
|
|
|
// TestDecodeAcceptsAnEmptyDocument keeps the strictness from breaking a legitimate state: a decision
|
|
// file the engine itself wrote with nothing in it, a file of whitespace, and — the case the first
|
|
// version of this list missed — a file holding only COMMENTS.
|
|
//
|
|
// yaml.Decoder reports a stream with no document as io.EOF where yaml.Unmarshal accepted it as "no
|
|
// records", and every caller here used the second one. A whitespace-only pre-check is NOT the same
|
|
// test: comment bytes are not blank, so a header-only reject list came back `seed: EOF` and broke a
|
|
// file that had always loaded — in a format whose own entries carry a `note` to be self-documenting,
|
|
// on the path that governs every book's glossary_seed. Found by review; reproduced against a build of
|
|
// HEAD before the fix.
|
|
func TestDecodeAcceptsAnEmptyDocument(t *testing.T) {
|
|
for _, body := range []string{"", "\n", " \n\n", "# declined terms\n", "# a\n# b\n", " # indented\n"} {
|
|
f, err := DecodeFile([]byte(body))
|
|
if err != nil || len(f.Terms) != 0 {
|
|
t.Errorf("document with no records %q: err=%v terms=%d, want no error and no terms", body, err, len(f.Terms))
|
|
}
|
|
r, err := DecodeRejects([]byte(body))
|
|
if err != nil || len(r.Rejects) != 0 {
|
|
t.Errorf("reject document with no records %q: err=%v rejects=%d, want no error and no rejects", body, err, len(r.Rejects))
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestDecodeRoundTripsEveryTermField is the guard against the strictness itself becoming the defect: a
|
|
// document naming every field of the schema must load, or a field added to Term without a matching tag
|
|
// would make previously valid files unloadable.
|
|
func TestDecodeRoundTripsEveryTermField(t *testing.T) {
|
|
body := `terms:
|
|
- src: 方源
|
|
dst: Фан Юань
|
|
type: name
|
|
sense: герой
|
|
gender: male
|
|
speech: сухая
|
|
decl: { invariant: false, forms: [Фан Юаня] }
|
|
translit_policy: palladius
|
|
first_person: я
|
|
nickname_translation: keep
|
|
since_ch: 1
|
|
until_ch: 100
|
|
status: approved
|
|
allow_short: true
|
|
note: n
|
|
aliases:
|
|
- { alias: 方, type: short }
|
|
voices:
|
|
- src: 方源
|
|
sense: герой
|
|
register: r
|
|
self_ref: я
|
|
address_default: informal
|
|
lexicon_markers: [a]
|
|
ng_lexicon: [b]
|
|
exemplars: [c]
|
|
brightness: high
|
|
since_ch: 1
|
|
until_ch: 2
|
|
addresses:
|
|
- speaker: 方源
|
|
speaker_sense: герой
|
|
addressee: 花月
|
|
addressee_sense: ""
|
|
register: informal
|
|
form: ты
|
|
closeness: близко
|
|
since_ch: 1
|
|
until_ch: 2
|
|
`
|
|
f, err := DecodeFile([]byte(body))
|
|
if err != nil {
|
|
t.Fatalf("a document naming every schema field must load: %v", err)
|
|
}
|
|
if len(f.Terms) != 1 || len(f.Voices) != 1 || len(f.Addresses) != 1 {
|
|
t.Fatalf("got %d terms, %d voices, %d addresses", len(f.Terms), len(f.Voices), len(f.Addresses))
|
|
}
|
|
if f.Terms[0].Decl == nil || len(f.Terms[0].Decl.Forms) != 1 || len(f.Terms[0].Aliases) != 1 {
|
|
t.Errorf("nested records lost: decl=%v aliases=%v", f.Terms[0].Decl, f.Terms[0].Aliases)
|
|
}
|
|
}
|