use super::*;
use crate::widget;
async fn said(
router: &Router,
path: &str,
body: &str,
headers: &[(&str, &str)],
) -> (StatusCode, String) {
let mut req = axum::http::Request::builder().method("POST").uri(path);
for (k, v) in headers {
req = req.header(*k, *v);
}
let req = req
.header("content-type", "application/json")
.body(Body::from(body.to_string()))
.unwrap();
let resp = router.clone().oneshot(req).await.unwrap();
let status = resp.status();
let bytes = axum::body::to_bytes(resp.into_body(), 8192).await.unwrap();
(status, String::from_utf8_lossy(&bytes).into_owned())
}
#[tokio::test]
async fn add_allows_only_what_the_card_showed() {
let (_tmp, app, _router, _leaves) = gated_app_with("snyvi-widget-card", |_| {});
let desk = app
.store
.create_desk("/tmp/cardwatch", Some("cardwatch"))
.unwrap()
.id;
let stage = widget::files::dir(&app.paths.config_dir)
.join(".proposed")
.join("100-ci");
std::fs::create_dir_all(&stage).unwrap();
std::fs::write(
stage.join("widget.json"),
r#"{"name":"ci","scope":"desk","run":{"command":"sh \"$SNYVI_WIDGET_DIR/run.sh\""}}"#,
)
.unwrap();
std::fs::write(stage.join("run.sh"), "echo shown").unwrap();
let card = crate::thread::Suggestion {
desk_id: desk,
kind: "widget".into(),
name: "ci".into(),
folder: stage.to_string_lossy().into(),
detail: serde_json::json!({ "where": "here", "hash": widget::files::hash(&stage).unwrap() }),
..Default::default()
};
let lasts = super::super::api_widget::Lasts::default();
std::fs::write(stage.join("run.sh"), "echo swapped").unwrap();
let why = super::super::api_widget::install_proposed(&app, &card, &lasts).unwrap_err();
assert!(why.contains("changed after it was proposed"), "{why}");
assert!(stage.is_dir(), "nothing moved in");
let old = crate::thread::Suggestion {
detail: serde_json::json!({ "where": "here" }),
..card.clone()
};
assert!(super::super::api_widget::install_proposed(&app, &old, &lasts).is_err());
std::fs::write(stage.join("run.sh"), "echo shown").unwrap();
super::super::api_widget::install_proposed(&app, &card, &lasts).unwrap();
let own = widget::files::folder_of(&app.paths.config_dir, desk, "ci");
let p = app
.store
.clocked(|c, _| widget::prefs(c, desk, "ci"))
.unwrap();
assert_eq!(p.trusted_hash, widget::files::hash(&own).unwrap());
}
#[tokio::test]
async fn the_token_turns_a_widget_off_and_widens_nothing() {
let (_tmp, router, l) = gated_router("snyvi-widget-widen");
let agent = [
("host", l.host.as_str()),
("authorization", l.bearer.as_str()),
];
let window = [
("host", l.host.as_str()),
("origin", l.origin.as_str()),
("x-snyvi-capability", l.cap.as_str()),
];
let path = "/api/widgets/git/prefs";
for body in [
r#"{"owner":7,"share":true}"#,
r#"{"owner":0,"desks":[]}"#,
r#"{"owner":0,"hidden":false}"#,
r#"{"owner":0,"until":0}"#,
r#"{"owner":0,"settings":{"base":"x"}}"#,
r#"{"owner":0,"ask_again":1}"#,
] {
let (code, _) = said(&router, path, body, &agent).await;
assert_eq!(code, StatusCode::FORBIDDEN, "the token widened: {body}");
let (code, _) = said(&router, path, body, &window).await;
assert_ne!(code, StatusCode::FORBIDDEN, "the window may: {body}");
}
let (code, _) = said(&router, path, r#"{"owner":0,"hidden":true}"#, &agent).await;
assert_ne!(code, StatusCode::FORBIDDEN, "off is anyone's");
}
#[tokio::test]
async fn a_no_holds_on_the_cli_until_the_reader_takes_it_back() {
let (_tmp, app, router, l) = gated_app_with("snyvi-widget-no", |_| {});
let desk = app
.store
.create_desk("/tmp/nohold", Some("nohold"))
.unwrap()
.id;
app.store
.clocked(|c, now| {
widget::ask_wait(c, desk, "deploy", "p1", "panel 1", "x", now)?;
widget::ask_answer(c, desk, "deploy", "no", 0, "", now).map(|_| ())
})
.unwrap();
let agent = [
("host", l.host.as_str()),
("authorization", l.bearer.as_str()),
];
let push = |body: &str| format!(r#"{{"name":"deploy","desk":{desk},"body":"{body}"}}"#);
let (code, why) = said(&router, "/api/widgets", &push("**3/5**"), &agent).await;
assert_eq!(code, StatusCode::CONFLICT, "{why}");
assert!(why.contains("not now"), "{why}");
let (code, _) = said(&router, "/api/widgets", &push(""), &agent).await;
assert!(code.is_success(), "clearing is still anyone's");
app.store
.clocked(|c, now| widget::ask_again(c, desk, "deploy", now))
.unwrap();
let (code, _) = said(&router, "/api/widgets", &push("**3/5**"), &agent).await;
assert!(code.is_success(), "taken back: a script's push goes in");
}