mod common;
use common::MemState;
use navi_notifier_core::model::EventKind;
use navi_notifier_core::traits::Source;
use navi_notifier_github::{GitHubSource, GitHubSourceConfig};
use serde_json::json;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
async fn mock_github(server: &MockServer, notifications: serde_json::Value, pr: serde_json::Value) {
Mock::given(method("GET"))
.and(path("/user"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "login": "me" })))
.mount(server)
.await;
Mock::given(method("GET"))
.and(path("/notifications"))
.respond_with(ResponseTemplate::new(200).set_body_json(notifications))
.mount(server)
.await;
Mock::given(method("GET"))
.and(path("/repos/acme/widgets/pulls/1"))
.respond_with(ResponseTemplate::new(200).set_body_json(pr))
.mount(server)
.await;
for sub in [
"/repos/acme/widgets/pulls/1/reviews",
"/repos/acme/widgets/pulls/1/comments",
"/repos/acme/widgets/issues/1/comments",
] {
Mock::given(method("GET"))
.and(path(sub))
.respond_with(ResponseTemplate::new(200).set_body_json(json!([])))
.mount(server)
.await;
}
}
fn source_for(server: &MockServer) -> GitHubSource {
GitHubSource::new(GitHubSourceConfig {
token: "test-token".into(),
api_base: Some(server.uri()),
})
.expect("build source")
}
fn pr_notification(server_uri: &str) -> serde_json::Value {
json!([{
"id": "thread-1",
"reason": "review_requested",
"updated_at": "2024-01-02T03:04:05Z",
"subject": {
"title": "Add gizmo",
"url": format!("{server_uri}/repos/acme/widgets/pulls/1"),
"type": "PullRequest"
},
"repository": {
"name": "widgets",
"owner": { "login": "acme" },
"html_url": "https://github.com/acme/widgets"
}
}])
}
fn open_pr(requested_reviewers: serde_json::Value) -> serde_json::Value {
json!({
"number": 1,
"title": "Add gizmo",
"html_url": "https://github.com/acme/widgets/pull/1",
"state": "open",
"draft": false,
"merged": false,
"updated_at": "2024-01-02T03:04:05Z",
"user": { "login": "octo" },
"requested_reviewers": requested_reviewers
})
}
#[tokio::test]
async fn poll_emits_outstanding_review_request() {
let server = MockServer::start().await;
mock_github(
&server,
pr_notification(&server.uri()),
open_pr(json!([{ "login": "me" }])),
)
.await;
let source = source_for(&server);
let state = MemState::default();
let events = source.poll(&state).await.expect("poll");
assert_eq!(events.len(), 1, "expected one event, got {events:?}");
assert_eq!(events[0].kind, EventKind::ReviewRequested);
assert_eq!(events[0].pull_request.number, 1);
assert_eq!(events[0].pull_request.repo.full_name(), "acme/widgets");
assert!(events[0].viewer.is_reviewer);
}
#[tokio::test]
async fn poll_backfills_nothing_when_not_involved() {
let server = MockServer::start().await;
mock_github(
&server,
pr_notification(&server.uri()),
open_pr(json!([{ "login": "someone-else" }])),
)
.await;
let source = source_for(&server);
let events = source.poll(&MemState::default()).await.expect("poll");
assert!(events.is_empty(), "expected no events, got {events:?}");
}
#[tokio::test]
async fn poll_ignores_non_pull_request_notifications() {
let server = MockServer::start().await;
let notifications = json!([{
"id": "thread-9",
"reason": "mention",
"updated_at": "2024-01-02T03:04:05Z",
"subject": {
"title": "a plain issue",
"url": format!("{}/repos/acme/widgets/issues/7", server.uri()),
"type": "Issue"
},
"repository": {
"name": "widgets",
"owner": { "login": "acme" },
"html_url": "https://github.com/acme/widgets"
}
}]);
Mock::given(method("GET"))
.and(path("/user"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "login": "me" })))
.mount(&server)
.await;
Mock::given(method("GET"))
.and(path("/notifications"))
.respond_with(ResponseTemplate::new(200).set_body_json(notifications))
.mount(&server)
.await;
let source = source_for(&server);
let events = source.poll(&MemState::default()).await.expect("poll");
assert!(events.is_empty());
}