| 482 | |
| 483 | #[test] |
| 484 | fn handles_flat_cursor_shape() { |
| 485 | // Cursor: event -> [ { command } ] (flat, no inner "hooks"). |
| 486 | let dir = tempfile::tempdir().unwrap(); |
| 487 | let target = dir.path().join("hooks.json"); |
| 488 | write( |
| 489 | &target, |
| 490 | &json!({ |
| 491 | "version": 1, |
| 492 | "hooks": { |
| 493 | "sessionStart": [ { "command": "my-own-cursor-hook" } ] |
| 494 | } |
| 495 | }), |
| 496 | ); |
| 497 | let manifest = dir.path().join("manifest.json"); |
| 498 | write( |
| 499 | &manifest, |
| 500 | &json!({ |
| 501 | "target": target.to_string_lossy(), |
| 502 | "command_prefix": "atomic agent hooks cursor", |
| 503 | "hooks": { |
| 504 | "sessionStart": [ { "command": "atomic agent hooks cursor session-start" } ], |
| 505 | "stop": [ { "command": "atomic agent hooks cursor stop" } ] |
| 506 | } |
| 507 | }), |
| 508 | ); |
| 509 | |
| 510 | let outcome = install_from_manifest(&manifest).unwrap(); |
| 511 | assert_eq!(outcome.added, 2); |
| 512 | |
| 513 | // Idempotent: re-run replaces our 2, keeps the user's hook. |
| 514 | let again = install_from_manifest(&manifest).unwrap(); |
| 515 | assert_eq!(again.added, 2); |
| 516 | assert_eq!(again.removed, 2); |
| 517 | |
| 518 | let written: Value = |
| 519 | serde_json::from_str(&std::fs::read_to_string(&target).unwrap()).unwrap(); |
| 520 | assert_eq!(written["version"], 1); |
| 521 | let start: Vec<&str> = written["hooks"]["sessionStart"] |
| 522 | .as_array() |
| 523 | .unwrap() |
| 524 | .iter() |
| 525 | .map(|e| e["command"].as_str().unwrap()) |
| 526 | .collect(); |
| 527 | assert!(start.contains(&"my-own-cursor-hook")); |
| 528 | assert!(start |
| 529 | .iter() |
| 530 | .any(|c| c.contains("atomic agent hooks cursor session-start"))); |
| 531 | } |
| 532 | |
| 533 | #[test] |
| 534 | fn merges_extra_settings_idempotently() { |