694d9e4e45
"что нового?" is a greeting, and the feed matcher claimed it: "нового" was a feed noun and "что" an ask. With no feeds block, which is what ships, the answer to hello was "я пока не читаю ленты — они не настроены". A newness word now needs a named topic or a real feed noun beside it. The topic prepositions lose "о" for the same class of reason: one rune of filler produced a category of whatever followed it, and then "по этой теме в лентах пока ничего". An undated feed was re-noted in full on every boot. Dated items are deduped against the durable mark, undated ones against a map that dies with the process, so five items became five more on the next start, stamped now, at the top of the recent-notes window. A crash loop made that a flood. The mark is now set for an undated feed too, and its existence marks the first poll after a restart as a resync: those items are recorded as seen rather than written. A burst larger than max_items lost its middle. The poll walked the feed newest-first, stopped at the cap, and marked the newest item written, which put everything below the cap behind the mark forever. The cap now applies to the oldest candidates and the mark follows what was written, so max_items paces instead of dropping. The category tag was read out loud: "Заголовок [технологии]" went through piper brackets and all, because the answer path took the whole first line. The tag is parsed off for reading and is now the only thing a topic is matched against. Matching the whole note meant "что нового про погоду" hit any tech headline whose link contained "pogod". Also: the charset comment on dec.Strict described something Strict does not do, and a skipped feed is named in the log. Found in review of #66.
59 lines
2.2 KiB
Go
59 lines
2.2 KiB
Go
package router
|
||
|
||
import "testing"
|
||
|
||
func TestParseFeedQuery(t *testing.T) {
|
||
cases := []struct {
|
||
text string
|
||
ok bool
|
||
category string
|
||
}{
|
||
{"что нового в лентах?", true, ""},
|
||
{"что нового по технологиям", true, "технологиям"},
|
||
{"какие новости?", true, ""},
|
||
{"что нового по технологиям?", true, "технологиям"},
|
||
{"расскажи новости про политику", true, "политику"},
|
||
{"что нового по новостям", true, ""},
|
||
{"what's new in the feeds?", true, ""},
|
||
{"any news about kubernetes", true, "kubernetes"},
|
||
// "что нового?" is a greeting. Claiming it made the shipping daemon
|
||
// answer hello with "я пока не читаю ленты — они не настроены".
|
||
{"что нового?", false, ""},
|
||
{"ну что нового", false, ""},
|
||
// A stray "о" is not a topic marker.
|
||
{"что нового в лентах, о боже", true, ""},
|
||
// Statements, not requests.
|
||
{"у меня новая лента в инстаграме", false, ""},
|
||
{"новости меня утомили", false, ""},
|
||
{"напомни полить цветы", false, ""},
|
||
{"", false, ""},
|
||
}
|
||
for _, c := range cases {
|
||
q, ok := ParseFeedQuery(c.text)
|
||
if ok != c.ok {
|
||
t.Errorf("ParseFeedQuery(%q) ok = %v, want %v", c.text, ok, c.ok)
|
||
continue
|
||
}
|
||
if ok && q.Category != c.category {
|
||
t.Errorf("ParseFeedQuery(%q) category = %q, want %q", c.text, q.Category, c.category)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestCategoryMatches(t *testing.T) {
|
||
// The inflected form he says must match the form the config spells.
|
||
if !CategoryMatches("технологии", "технологиям") {
|
||
t.Error("inflected category did not match")
|
||
}
|
||
if CategoryMatches("технологии", "политику") {
|
||
t.Error("unrelated category matched")
|
||
}
|
||
// The tag, not the note. The link in a tech headline is not a weather report.
|
||
if CategoryMatches("технологии", "погоду") {
|
||
t.Error("a tech note matched a weather question")
|
||
}
|
||
if !CategoryMatches("anything", "") {
|
||
t.Error("an empty category must match everything")
|
||
}
|
||
}
|