feat: implement entry variant tagging, backend action handlers, batch variant loading, and rule application API.
This commit is contained in:
@@ -25,16 +25,17 @@ func TestRuleEvaluator(t *testing.T) {
|
||||
TextValue: `["go", "compiler", "release"]`,
|
||||
},
|
||||
},
|
||||
PublishedAt: time.Now(),
|
||||
PublishedAt: time.Now().Add(-2 * time.Hour),
|
||||
CreatedAt: time.Now().Add(-2 * time.Hour),
|
||||
}
|
||||
|
||||
// Test 1: Title contains Go AND FeedID == feed-hn
|
||||
// Test 1: Title contains Go AND FeedID in list
|
||||
rule1 := models.Rule{
|
||||
ID: "r1",
|
||||
Name: "Tech Feeds",
|
||||
Name: "Tech Feeds in list",
|
||||
MatchMode: "all",
|
||||
Conditions: []models.RuleCondition{
|
||||
{Field: "feed_id", Operator: "eq", Value: "feed-hn"},
|
||||
{Field: "feed_id", Operator: "in", Value: []string{"feed-hn", "feed-36kr", "feed-ft"}},
|
||||
{Field: "title", Operator: "contains", Value: "Go 1.24"},
|
||||
},
|
||||
}
|
||||
@@ -42,29 +43,76 @@ func TestRuleEvaluator(t *testing.T) {
|
||||
t.Error("expected rule1 to match entry")
|
||||
}
|
||||
|
||||
// Test 2: Negative condition (author != Python Team)
|
||||
// Test 2: not_in operator
|
||||
rule2 := models.Rule{
|
||||
ID: "r2",
|
||||
Name: "Author check",
|
||||
Name: "Feed not in noise",
|
||||
MatchMode: "all",
|
||||
Conditions: []models.RuleCondition{
|
||||
{Field: "author", Operator: "neq", Value: "Python Team"},
|
||||
{Field: "feed_id", Operator: "not_in", Value: "feed-spam,feed-ads"},
|
||||
},
|
||||
}
|
||||
if !eval.Evaluate(rule2, entry) {
|
||||
t.Error("expected rule2 to match entry")
|
||||
}
|
||||
|
||||
// Test 3: Variant matching (variants.keywords contains compiler)
|
||||
// Test 3: contains_any operator
|
||||
rule3 := models.Rule{
|
||||
ID: "r3",
|
||||
Name: "Keywords check",
|
||||
Name: "Title match any keyword",
|
||||
MatchMode: "all",
|
||||
Conditions: []models.RuleCondition{
|
||||
{Field: "variants.keywords", Operator: "contains", Value: "compiler"},
|
||||
{Field: "title", Operator: "contains_any", Value: "Rust, Python, Go 1.24"},
|
||||
},
|
||||
}
|
||||
if !eval.Evaluate(rule3, entry) {
|
||||
t.Error("expected rule3 to match entry variant")
|
||||
t.Error("expected rule3 to match entry with contains_any")
|
||||
}
|
||||
|
||||
// Test 4: exists operator on variants
|
||||
rule4 := models.Rule{
|
||||
ID: "r4",
|
||||
Name: "Keywords exist",
|
||||
MatchMode: "all",
|
||||
Conditions: []models.RuleCondition{
|
||||
{Field: "variants.keywords", Operator: "exists"},
|
||||
{Field: "variants.nonexistent", Operator: "not_exists"},
|
||||
},
|
||||
}
|
||||
if !eval.Evaluate(rule4, entry) {
|
||||
t.Error("expected rule4 to evaluate variant existence correctly")
|
||||
}
|
||||
|
||||
// Test 5: Timestamp within_last_hours
|
||||
rule5 := models.Rule{
|
||||
ID: "r5",
|
||||
Name: "Recent article",
|
||||
MatchMode: "all",
|
||||
Conditions: []models.RuleCondition{
|
||||
{Field: "published_at", Operator: "within_last_hours", Value: 24},
|
||||
},
|
||||
}
|
||||
if !eval.Evaluate(rule5, entry) {
|
||||
t.Error("expected rule5 to match article within last 24h")
|
||||
}
|
||||
|
||||
// Test 6: Safety validator
|
||||
unsafeRule := models.Rule{
|
||||
Trigger: "variant_change",
|
||||
Conditions: []models.RuleCondition{
|
||||
{Field: "variants.tags", Operator: "exists"},
|
||||
},
|
||||
Actions: []models.RuleAction{
|
||||
{
|
||||
Type: "set_variant",
|
||||
Params: map[string]interface{}{
|
||||
"variant_key": "tags",
|
||||
"value": "new-tag",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := ValidateRuleSafety(unsafeRule); err == nil {
|
||||
t.Error("expected ValidateRuleSafety to detect cyclic loop")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user