KeiSeiKit-1.0/_primitives/_rust/kei-notify-slack/tests/slack_smoke.rs
Parfii-bot 0be354a920 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

104 lines
3.7 KiB
Rust

// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 <author org>
//
//! `wiremock`-driven smoke tests for [`SlackChannel`]. No live Slack calls.
use kei_notify_slack::{Error, SlackChannel};
use kei_runtime_core::traits::notify::{Notification, NotifyChannel, NotifySeverity};
use kei_runtime_core::{DnaBuilder, HasDna};
use serde_json::Value;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, Request, ResponseTemplate};
fn make_notification(severity: NotifySeverity, subject: &str, body: &str) -> Notification {
let dna = DnaBuilder::new("notification")
.cap("NT")
.scope("test/slack")
.body(b"smoke")
.build()
.unwrap();
let parent = DnaBuilder::new("primitive")
.cap("PR")
.scope("test/slack-parent")
.body(b"parent")
.build()
.unwrap();
Notification {
dna,
parent_dna: parent,
subject: subject.into(),
body_text: body.into(),
body_html: None,
severity,
tags: Vec::new(),
}
}
#[tokio::test]
async fn send_info_200() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/services/T1/B1/abc"))
.respond_with(ResponseTemplate::new(200).set_body_string("ok"))
.mount(&server)
.await;
let url = format!("{}/services/T1/B1/abc", server.uri());
let ch = SlackChannel::with_url(None, url).unwrap();
let n = make_notification(NotifySeverity::Info, "hello", "world");
ch.send(&n).await.expect("send ok");
assert_eq!(ch.channel_name(), "slack");
}
#[tokio::test]
async fn send_error_includes_danger_color() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/services/T2/B2/xyz"))
.respond_with(ResponseTemplate::new(200).set_body_string("ok"))
.mount(&server)
.await;
let url = format!("{}/services/T2/B2/xyz", server.uri());
let ch = SlackChannel::with_url(None, url).unwrap();
let n = make_notification(NotifySeverity::Error, "boom", "fatal");
ch.send(&n).await.expect("send ok");
// Pull the captured request and assert its JSON body shape.
let received = server.received_requests().await.unwrap();
assert_eq!(received.len(), 1);
let req: &Request = &received[0];
let body: Value = serde_json::from_slice(&req.body).unwrap();
assert_eq!(body["text"], "boom");
assert_eq!(body["attachments"][0]["color"], "danger");
assert_eq!(body["attachments"][0]["title"], "boom");
assert_eq!(body["attachments"][0]["text"], "fatal");
}
#[tokio::test]
async fn send_500_returns_api_error() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/services/T3/B3/fail"))
.respond_with(ResponseTemplate::new(500).set_body_string("server burning"))
.mount(&server)
.await;
let url = format!("{}/services/T3/B3/fail", server.uri());
let ch = SlackChannel::with_url(None, url).unwrap();
let n = make_notification(NotifySeverity::Warn, "hot", "smoke");
let err = ch.send_raw(&n).await.expect_err("must fail");
match err {
Error::Api(msg) => {
assert!(msg.contains("500"), "msg should include status: {msg}");
}
other => panic!("expected Error::Api, got {other:?}"),
}
}
#[tokio::test]
async fn dna_has_sk_cap() {
let ch = SlackChannel::with_url(None, "http://example.invalid/services/x/y/z").unwrap();
let caps = ch.dna().caps();
assert!(caps.contains("SK"), "DNA caps must include SK, got {caps}");
assert!(caps.contains("PR"), "DNA caps must include PR, got {caps}");
assert!(caps.contains("AP"), "DNA caps must include AP, got {caps}");
assert_eq!(ch.dna().role(), "primitive");
}