mirror of
https://github.com/samvallad33/vestige.git
synced 2026-05-08 15:22:37 +02:00
fix(intention): accept snake_case in_minutes / file_pattern on TriggerSpec
The public JSON schema in schema() declares `in_minutes` and `file_pattern` in snake_case, but TriggerSpec uses `#[serde(rename_all = "camelCase")]` which makes serde expect `inMinutes` / `filePattern`. Snake_case inputs are silently dropped to None, so time-based intentions with `in_minutes` never fire (triggerAt becomes null) and file_pattern-only context intentions never match. Added `#[serde(alias = ...)]` so both naming conventions deserialize correctly — purely additive, existing camelCase callers unaffected. Two regression tests added, verified to FAIL without the aliases (negative control confirmed the snake_case duration test sees `triggerAt: null` and the file_pattern test sees an empty `triggered` array). Both pass with the fix. Full crate suite: 408/408 passing. Related to #25 (Bug #8 was half-fixed — check-side re-derivation works, but the set-side was still dropping the value before it could be persisted).
This commit is contained in:
parent
04781a95e2
commit
f97dc7d084
1 changed files with 73 additions and 0 deletions
|
|
@ -152,8 +152,10 @@ struct TriggerSpec {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
trigger_type: Option<String>,
|
trigger_type: Option<String>,
|
||||||
at: Option<String>,
|
at: Option<String>,
|
||||||
|
#[serde(alias = "in_minutes")]
|
||||||
in_minutes: Option<i64>,
|
in_minutes: Option<i64>,
|
||||||
codebase: Option<String>,
|
codebase: Option<String>,
|
||||||
|
#[serde(alias = "file_pattern")]
|
||||||
file_pattern: Option<String>,
|
file_pattern: Option<String>,
|
||||||
topic: Option<String>,
|
topic: Option<String>,
|
||||||
condition: Option<String>,
|
condition: Option<String>,
|
||||||
|
|
@ -819,6 +821,77 @@ mod tests {
|
||||||
assert!(value["triggerAt"].is_string());
|
assert!(value["triggerAt"].is_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_set_action_with_duration_trigger_snake_case() {
|
||||||
|
// The public JSON schema (see schema() above) declares `in_minutes` in
|
||||||
|
// snake_case. The TriggerSpec struct uses `rename_all = "camelCase"` so
|
||||||
|
// without an explicit `#[serde(alias = "in_minutes")]` the snake_case
|
||||||
|
// input is silently dropped (becomes None), `triggerAt` becomes null,
|
||||||
|
// and the time-based intention never fires.
|
||||||
|
let (storage, _dir) = test_storage().await;
|
||||||
|
let args = serde_json::json!({
|
||||||
|
"action": "set",
|
||||||
|
"description": "Check build status",
|
||||||
|
"trigger": {
|
||||||
|
"type": "time",
|
||||||
|
"in_minutes": 30
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let result = execute(&storage, &test_cognitive(), Some(args)).await;
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let value = result.unwrap();
|
||||||
|
assert!(
|
||||||
|
value["triggerAt"].is_string(),
|
||||||
|
"snake_case in_minutes should derive triggerAt; got: {:?}",
|
||||||
|
value["triggerAt"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_set_action_with_file_pattern_snake_case() {
|
||||||
|
// The public JSON schema declares `file_pattern` in snake_case. Verify
|
||||||
|
// it survives deserialization by setting an intention with ONLY
|
||||||
|
// file_pattern (no codebase — otherwise the check-side codebase branch
|
||||||
|
// would short-circuit and mask a dropped file_pattern field).
|
||||||
|
//
|
||||||
|
// Note: file_pattern matching currently uses substring containment, not
|
||||||
|
// glob, so the "pattern" must be a plain substring of the file path.
|
||||||
|
let (storage, _dir) = test_storage().await;
|
||||||
|
let args = serde_json::json!({
|
||||||
|
"action": "set",
|
||||||
|
"description": "Review test files",
|
||||||
|
"trigger": {
|
||||||
|
"type": "context",
|
||||||
|
"file_pattern": ".test.cjs"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let result = execute(&storage, &test_cognitive(), Some(args)).await;
|
||||||
|
assert!(
|
||||||
|
result.is_ok(),
|
||||||
|
"set should succeed with snake_case file_pattern"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check should fire when a matching file is in context.
|
||||||
|
let check_args = serde_json::json!({
|
||||||
|
"action": "check",
|
||||||
|
"context": {
|
||||||
|
"file": "tests/neural-cascade.test.cjs"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let check = execute(&storage, &test_cognitive(), Some(check_args))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let triggered = check["triggered"].as_array().expect("triggered array");
|
||||||
|
assert!(
|
||||||
|
!triggered.is_empty(),
|
||||||
|
"file_pattern must survive snake_case deserialization and match on file substring; \
|
||||||
|
got triggered: {:?}, pending: {:?}",
|
||||||
|
check["triggered"],
|
||||||
|
check["pending"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_set_action_with_deadline() {
|
async fn test_set_action_with_deadline() {
|
||||||
let (storage, _dir) = test_storage().await;
|
let (storage, _dir) = test_storage().await;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue