use vapora_channels::ChannelRegistry; use vapora_workflow_engine::config::WorkflowsConfig; /// Full TOML round-trip: channels section + per-workflow notification targets. const TOML_WITH_CHANNELS: &str = r#" [engine] max_parallel_tasks = 4 workflow_timeout = 3600 approval_gates_enabled = false [channels.team-slack] type = "slack" webhook_url = "https://hooks.slack.com/services/TEST/TEST/TEST" [channels.ops-telegram] type = "telegram" bot_token = "123:TEST" chat_id = "-100999" [[workflows]] name = "deploy-prod" trigger = "manual" [workflows.notifications] on_completed = ["team-slack"] on_failed = ["team-slack", "ops-telegram"] on_approval_required = ["team-slack"] [[workflows.stages]] name = "build" agents = ["developer"] [[workflows.stages]] name = "deploy" agents = ["deployer"] approval_required = true "#; /// Workflow with no [channels] section — should parse without error and leave /// the channel map empty (registry skipped by orchestrator). const TOML_WITHOUT_CHANNELS: &str = r#" [engine] max_parallel_tasks = 4 workflow_timeout = 3600 approval_gates_enabled = false [[workflows]] name = "ci-pipeline" trigger = "manual" [[workflows.stages]] name = "test" agents = ["developer"] "#; #[test] fn channels_section_parses_into_config() { let config: WorkflowsConfig = toml::from_str(TOML_WITH_CHANNELS).expect("must parse"); assert_eq!(config.channels.len(), 2); assert!(config.channels.contains_key("team-slack")); assert!(config.channels.contains_key("ops-telegram")); } #[test] fn notification_targets_parse_per_workflow() { let config: WorkflowsConfig = toml::from_str(TOML_WITH_CHANNELS).expect("must parse"); let wf = config.get_workflow("deploy-prod").expect("workflow exists"); assert_eq!(wf.notifications.on_completed, ["team-slack"]); assert_eq!(wf.notifications.on_failed, ["team-slack", "ops-telegram"]); assert_eq!(wf.notifications.on_approval_required, ["team-slack"]); } #[test] fn missing_channels_section_defaults_to_empty() { let config: WorkflowsConfig = toml::from_str(TOML_WITHOUT_CHANNELS).expect("must parse"); assert!(config.channels.is_empty()); } #[test] fn missing_notifications_block_defaults_to_empty_vecs() { let config: WorkflowsConfig = toml::from_str(TOML_WITHOUT_CHANNELS).expect("must parse"); let wf = config.get_workflow("ci-pipeline").expect("workflow exists"); assert!(wf.notifications.on_completed.is_empty()); assert!(wf.notifications.on_failed.is_empty()); assert!(wf.notifications.on_approval_required.is_empty()); } #[test] fn channel_registry_builds_from_config() { let config: WorkflowsConfig = toml::from_str(TOML_WITH_CHANNELS).expect("must parse"); let registry = ChannelRegistry::from_map(config.channels).expect("registry must build"); let mut names = registry.channel_names(); names.sort_unstable(); assert_eq!(names, ["ops-telegram", "team-slack"]); } #[test] fn validation_passes_with_channels_and_notifications() { let config: WorkflowsConfig = toml::from_str(TOML_WITH_CHANNELS).expect("must parse"); config.validate().expect("validation must pass"); }