| 520 | |
| 521 | #[test] |
| 522 | fn handles_flat_cursor_shape() { |
| 523 | // Cursor: event -> [ { command } ] (flat, no inner "hooks"). |
| 524 | let dir = tempfile::tempdir().unwrap(); |
| 525 | let target = dir.path().join("hooks.json"); |
| 526 | write( |
| 527 | &target, |
| 528 | &json!({ |
| 529 | "version": 1, |
| 530 | "hooks": { |
| 531 | "sessionStart": [ { "command": "my-own-cursor-hook" } ] |
| 532 | } |
| 533 | }), |
| 534 | ); |
| 535 | let manifest = dir.path().join("manifest.json"); |
| 536 | write( |
| 537 | &manifest, |
| 538 | &json!({ |
| 539 | "target": target.to_string_lossy(), |
| 540 | "command_prefix": "atomic agent hooks cursor", |
| 541 | "hooks": { |
| 542 | "sessionStart": [ { "command": "atomic agent hooks cursor session-start" } ], |
| 543 | "stop": [ { "command": "atomic agent hooks cursor stop" } ] |
| 544 | } |
| 545 | }), |
| 546 | ); |
| 547 | |
| 548 | let outcome = install_from_manifest(&manifest).unwrap(); |
| 549 | assert_eq!(outcome.added, 2); |
| 550 | |
| 551 | // Idempotent: re-run replaces our 2, keeps the user's hook. |
| 552 | let again = install_from_manifest(&manifest).unwrap(); |
| 553 | assert_eq!(again.added, 2); |
| 554 | assert_eq!(again.removed, 2); |
| 555 | |
| 556 | let written: Value = |
| 557 | serde_json::from_str(&std::fs::read_to_string(&target).unwrap()).unwrap(); |
| 558 | assert_eq!(written["version"], 1); |
| 559 | let start: Vec<&str> = written["hooks"]["sessionStart"] |
| 560 | .as_array() |
| 561 | .unwrap() |
| 562 | .iter() |
| 563 | .map(|e| e["command"].as_str().unwrap()) |
| 564 | .collect(); |
| 565 | assert!(start.contains(&"my-own-cursor-hook")); |
| 566 | assert!(start |
| 567 | .iter() |
| 568 | .any(|c| c.contains("atomic agent hooks cursor session-start"))); |
| 569 | } |
| 570 | |
| 571 | #[test] |
| 572 | fn merges_extra_settings_idempotently() { |