Quellcode durchsuchen

Make it so that the API URLs are configurable for testing

Eric Huss vor 1 Jahr
Ursprung
Commit
3d5e2157c9
5 geänderte Dateien mit 106 neuen und 84 gelöschten Zeilen
  1. 1 2
      src/actions.rs
  2. 101 76
      src/github.rs
  3. 1 2
      src/handlers/docs_update.rs
  4. 1 3
      src/main.rs
  5. 2 1
      src/team_data.rs

+ 1 - 2
src/actions.rs

@@ -3,7 +3,6 @@ use std::collections::HashMap;
 use std::sync::Arc;
 
 use async_trait::async_trait;
-use reqwest::Client;
 use serde::{Deserialize, Serialize};
 use tera::{Context, Tera};
 
@@ -93,7 +92,7 @@ pub fn to_human(d: DateTime<Utc>) -> String {
 #[async_trait]
 impl<'a> Action for Step<'a> {
     async fn call(&self) -> anyhow::Result<String> {
-        let gh = GithubClient::new_with_default_token(Client::new());
+        let gh = GithubClient::new_from_env();
 
         // retrieve all Rust compiler meetings
         // from today for 7 days

+ 101 - 76
src/github.rs

@@ -118,7 +118,7 @@ impl GithubClient {
                 .client
                 .execute(
                     self.client
-                        .get("https://api.github.com/rate_limit")
+                        .get(&format!("{}/rate_limit", self.api_url))
                         .configure(self)
                         .build()
                         .unwrap(),
@@ -171,7 +171,9 @@ impl GithubClient {
 
 impl User {
     pub async fn current(client: &GithubClient) -> anyhow::Result<Self> {
-        client.json(client.get("https://api.github.com/user")).await
+        client
+            .json(client.get(&format!("{}/user", client.api_url)))
+            .await
     }
 
     pub async fn is_team_member<'a>(&'a self, client: &'a GithubClient) -> anyhow::Result<bool> {
@@ -419,10 +421,10 @@ impl fmt::Display for IssueRepository {
 }
 
 impl IssueRepository {
-    fn url(&self) -> String {
+    fn url(&self, client: &GithubClient) -> String {
         format!(
-            "https://api.github.com/repos/{}/{}",
-            self.organization, self.repository
+            "{}/repos/{}/{}",
+            client.api_url, self.organization, self.repository
         )
     }
 
@@ -432,7 +434,7 @@ impl IssueRepository {
 
     async fn has_label(&self, client: &GithubClient, label: &str) -> anyhow::Result<bool> {
         #[allow(clippy::redundant_pattern_matching)]
-        let url = format!("{}/labels/{}", self.url(), label);
+        let url = format!("{}/labels/{}", self.url(client), label);
         match client.send_req(client.get(&url)).await {
             Ok(_) => Ok(true),
             Err(e) => {
@@ -502,7 +504,7 @@ impl Issue {
     }
 
     pub async fn get_comment(&self, client: &GithubClient, id: usize) -> anyhow::Result<Comment> {
-        let comment_url = format!("{}/issues/comments/{}", self.repository().url(), id);
+        let comment_url = format!("{}/issues/comments/{}", self.repository().url(client), id);
         let comment = client.json(client.get(&comment_url)).await?;
         Ok(comment)
     }
@@ -511,7 +513,7 @@ impl Issue {
     pub async fn get_first_comment(&self, client: &GithubClient) -> anyhow::Result<Vec<Comment>> {
         let comment_url = format!(
             "{}/issues/{}/comments?page=1&per_page=1",
-            self.repository().url(),
+            self.repository().url(client),
             self.number,
         );
         Ok(client
@@ -526,7 +528,7 @@ impl Issue {
     ) -> anyhow::Result<Vec<Comment>> {
         let comment_url = format!(
             "{}/issues/{}/comments?page=1&per_page=100",
-            self.repository().url(),
+            self.repository().url(client),
             self.number,
         );
         Ok(client
@@ -535,7 +537,7 @@ impl Issue {
     }
 
     pub async fn edit_body(&self, client: &GithubClient, body: &str) -> anyhow::Result<()> {
-        let edit_url = format!("{}/issues/{}", self.repository().url(), self.number);
+        let edit_url = format!("{}/issues/{}", self.repository().url(client), self.number);
         #[derive(serde::Serialize)]
         struct ChangedIssue<'a> {
             body: &'a str,
@@ -553,7 +555,7 @@ impl Issue {
         id: usize,
         new_body: &str,
     ) -> anyhow::Result<()> {
-        let comment_url = format!("{}/issues/comments/{}", self.repository().url(), id);
+        let comment_url = format!("{}/issues/comments/{}", self.repository().url(client), id);
         #[derive(serde::Serialize)]
         struct NewComment<'a> {
             body: &'a str,
@@ -574,8 +576,13 @@ impl Issue {
         struct PostComment<'a> {
             body: &'a str,
         }
+        let comments_path = self
+            .comments_url
+            .strip_prefix("https://api.github.com")
+            .expect("expected api host");
+        let comments_url = format!("{}{comments_path}", client.api_url);
         client
-            .send_req(client.post(&self.comments_url).json(&PostComment { body }))
+            .send_req(client.post(&comments_url).json(&PostComment { body }))
             .await
             .context("failed to post comment")?;
         Ok(())
@@ -586,7 +593,7 @@ impl Issue {
         // DELETE /repos/:owner/:repo/issues/:number/labels/{name}
         let url = format!(
             "{repo_url}/issues/{number}/labels/{name}",
-            repo_url = self.repository().url(),
+            repo_url = self.repository().url(client),
             number = self.number,
             name = label,
         );
@@ -618,7 +625,7 @@ impl Issue {
         // repo_url = https://api.github.com/repos/Codertocat/Hello-World
         let url = format!(
             "{repo_url}/issues/{number}/labels",
-            repo_url = self.repository().url(),
+            repo_url = self.repository().url(client),
             number = self.number
         );
 
@@ -685,7 +692,7 @@ impl Issue {
         log::info!("remove {:?} assignees for {}", selection, self.global_id());
         let url = format!(
             "{repo_url}/issues/{number}/assignees",
-            repo_url = self.repository().url(),
+            repo_url = self.repository().url(client),
             number = self.number
         );
 
@@ -725,7 +732,7 @@ impl Issue {
         log::info!("add_assignee {} for {}", user, self.global_id());
         let url = format!(
             "{repo_url}/issues/{number}/assignees",
-            repo_url = self.repository().url(),
+            repo_url = self.repository().url(client),
             number = self.number
         );
 
@@ -787,7 +794,7 @@ impl Issue {
     }
 
     pub async fn close(&self, client: &GithubClient) -> anyhow::Result<()> {
-        let edit_url = format!("{}/issues/{}", self.repository().url(), self.number);
+        let edit_url = format!("{}/issues/{}", self.repository().url(client), self.number);
         #[derive(serde::Serialize)]
         struct CloseIssue<'a> {
             state: &'a str,
@@ -819,7 +826,10 @@ impl Issue {
         let diff = pr
             .files_changed
             .get_or_try_init::<anyhow::Error, _, _>(|| async move {
-                let url = format!("{}/compare/{before}...{after}", self.repository().url());
+                let url = format!(
+                    "{}/compare/{before}...{after}",
+                    self.repository().url(client)
+                );
                 let mut req = client.get(&url);
                 req = req.header("Accept", "application/vnd.github.v3.diff");
                 let (diff, _) = client
@@ -845,7 +855,7 @@ impl Issue {
         loop {
             let req = client.get(&format!(
                 "{}/pulls/{}/commits?page={page}&per_page=100",
-                self.repository().url(),
+                self.repository().url(client),
                 self.number
             ));
 
@@ -867,7 +877,7 @@ impl Issue {
 
         let req = client.get(&format!(
             "{}/pulls/{}/files",
-            self.repository().url(),
+            self.repository().url(client),
             self.number
         ));
         Ok(client.json(req).await?)
@@ -1055,11 +1065,8 @@ struct Ordering<'a> {
 }
 
 impl Repository {
-    const GITHUB_API_URL: &'static str = "https://api.github.com";
-    const GITHUB_GRAPHQL_API_URL: &'static str = "https://api.github.com/graphql";
-
-    fn url(&self) -> String {
-        format!("{}/repos/{}", Repository::GITHUB_API_URL, self.full_name)
+    fn url(&self, client: &GithubClient) -> String {
+        format!("{}/repos/{}", client.api_url, self.full_name)
     }
 
     pub fn owner(&self) -> &str {
@@ -1121,11 +1128,17 @@ impl Repository {
         let mut issues = vec![];
         loop {
             let url = if use_search_api {
-                self.build_search_issues_url(&filters, include_labels, exclude_labels, ordering)
+                self.build_search_issues_url(
+                    client,
+                    &filters,
+                    include_labels,
+                    exclude_labels,
+                    ordering,
+                )
             } else if is_pr {
-                self.build_pulls_url(&filters, include_labels, ordering)
+                self.build_pulls_url(client, &filters, include_labels, ordering)
             } else {
-                self.build_issues_url(&filters, include_labels, ordering)
+                self.build_issues_url(client, &filters, include_labels, ordering)
             };
 
             let result = client.get(&url);
@@ -1154,24 +1167,27 @@ impl Repository {
 
     fn build_issues_url(
         &self,
+        client: &GithubClient,
         filters: &Vec<(&str, &str)>,
         include_labels: &Vec<&str>,
         ordering: Ordering<'_>,
     ) -> String {
-        self.build_endpoint_url("issues", filters, include_labels, ordering)
+        self.build_endpoint_url(client, "issues", filters, include_labels, ordering)
     }
 
     fn build_pulls_url(
         &self,
+        client: &GithubClient,
         filters: &Vec<(&str, &str)>,
         include_labels: &Vec<&str>,
         ordering: Ordering<'_>,
     ) -> String {
-        self.build_endpoint_url("pulls", filters, include_labels, ordering)
+        self.build_endpoint_url(client, "pulls", filters, include_labels, ordering)
     }
 
     fn build_endpoint_url(
         &self,
+        client: &GithubClient,
         endpoint: &str,
         filters: &Vec<(&str, &str)>,
         include_labels: &Vec<&str>,
@@ -1194,15 +1210,13 @@ impl Repository {
             .join("&");
         format!(
             "{}/repos/{}/{}?{}",
-            Repository::GITHUB_API_URL,
-            self.full_name,
-            endpoint,
-            filters
+            client.api_url, self.full_name, endpoint, filters
         )
     }
 
     fn build_search_issues_url(
         &self,
+        client: &GithubClient,
         filters: &Vec<(&str, &str)>,
         include_labels: &Vec<&str>,
         exclude_labels: &Vec<&str>,
@@ -1227,7 +1241,7 @@ impl Repository {
             .join("+");
         format!(
             "{}/search/issues?q={}&sort={}&order={}&per_page={}&page={}",
-            Repository::GITHUB_API_URL,
+            client.api_url,
             filters,
             ordering.sort,
             ordering.direction,
@@ -1247,7 +1261,10 @@ impl Repository {
         let mut commits = Vec::new();
         let mut page = 1;
         loop {
-            let url = format!("{}/commits?sha={end}&per_page=100&page={page}", self.url());
+            let url = format!(
+                "{}/commits?sha={end}&per_page=100&page={page}",
+                self.url(client)
+            );
             let mut this_page: Vec<GithubCommit> = client
                 .json(client.get(&url))
                 .await
@@ -1265,7 +1282,7 @@ impl Repository {
 
     /// Retrieves a git commit for the given SHA.
     pub async fn git_commit(&self, client: &GithubClient, sha: &str) -> anyhow::Result<GitCommit> {
-        let url = format!("{}/git/commits/{sha}", self.url());
+        let url = format!("{}/git/commits/{sha}", self.url(client));
         client
             .json(client.get(&url))
             .await
@@ -1280,7 +1297,7 @@ impl Repository {
         parents: &[&str],
         tree: &str,
     ) -> anyhow::Result<GitCommit> {
-        let url = format!("{}/git/commits", self.url());
+        let url = format!("{}/git/commits", self.url(client));
         client
             .json(client.post(&url).json(&serde_json::json!({
                 "message": message,
@@ -1297,7 +1314,7 @@ impl Repository {
         client: &GithubClient,
         refname: &str,
     ) -> anyhow::Result<GitReference> {
-        let url = format!("{}/git/ref/{}", self.url(), refname);
+        let url = format!("{}/git/ref/{}", self.url(client), refname);
         client
             .json(client.get(&url))
             .await
@@ -1311,7 +1328,7 @@ impl Repository {
         refname: &str,
         sha: &str,
     ) -> anyhow::Result<GitReference> {
-        let url = format!("{}/git/refs/{}", self.url(), refname);
+        let url = format!("{}/git/refs/{}", self.url(client), refname);
         client
             .json(client.patch(&url).json(&serde_json::json!({
                 "sha": sha,
@@ -1362,7 +1379,7 @@ impl Repository {
             let query = RecentCommits::build(args.clone());
             let data = client
                 .json::<cynic::GraphQlResponse<RecentCommits>>(
-                    client.post(Repository::GITHUB_GRAPHQL_API_URL).json(&query),
+                    client.post(&client.graphql_url).json(&query),
                 )
                 .await
                 .with_context(|| {
@@ -1501,7 +1518,7 @@ impl Repository {
         base_tree: &str,
         tree: &[GitTreeEntry],
     ) -> anyhow::Result<GitTreeObject> {
-        let url = format!("{}/git/trees", self.url());
+        let url = format!("{}/git/trees", self.url(client));
         client
             .json(client.post(&url).json(&serde_json::json!({
                 "base_tree": base_tree,
@@ -1526,7 +1543,7 @@ impl Repository {
         path: &str,
         refname: Option<&str>,
     ) -> anyhow::Result<Submodule> {
-        let mut url = format!("{}/contents/{}", self.url(), path);
+        let mut url = format!("{}/contents/{}", self.url(client), path);
         if let Some(refname) = refname {
             url.push_str("?ref=");
             url.push_str(refname);
@@ -1548,7 +1565,7 @@ impl Repository {
         base: &str,
         body: &str,
     ) -> anyhow::Result<Issue> {
-        let url = format!("{}/pulls", self.url());
+        let url = format!("{}/pulls", self.url(client));
         let mut issue: Issue = client
             .json(client.post(&url).json(&serde_json::json!({
                 "title": title,
@@ -1571,7 +1588,7 @@ impl Repository {
     ///
     /// **Warning**: This will to a force update if there are conflicts.
     pub async fn merge_upstream(&self, client: &GithubClient, branch: &str) -> anyhow::Result<()> {
-        let url = format!("{}/merge-upstream", self.url());
+        let url = format!("{}/merge-upstream", self.url(client));
         let merge_error = match client
             .send_req(client.post(&url).json(&serde_json::json!({
                 "branch": branch,
@@ -1661,7 +1678,7 @@ impl Repository {
     }
 
     pub async fn get_issue(&self, client: &GithubClient, issue_num: u64) -> anyhow::Result<Issue> {
-        let url = format!("{}/pulls/{issue_num}", self.url());
+        let url = format!("{}/pulls/{issue_num}", self.url(client));
         client
             .json(client.get(&url))
             .await
@@ -1939,15 +1956,32 @@ fn get_token_from_git_config() -> anyhow::Result<String> {
 pub struct GithubClient {
     token: String,
     client: Client,
+    api_url: String,
+    graphql_url: String,
+    raw_url: String,
 }
 
 impl GithubClient {
-    pub fn new(client: Client, token: String) -> Self {
-        GithubClient { client, token }
+    pub fn new(token: String, api_url: String, graphql_url: String, raw_url: String) -> Self {
+        GithubClient {
+            client: Client::new(),
+            token,
+            api_url,
+            graphql_url,
+            raw_url,
+        }
     }
 
-    pub fn new_with_default_token(client: Client) -> Self {
-        Self::new(client, default_token_from_env())
+    pub fn new_from_env() -> Self {
+        Self::new(
+            default_token_from_env(),
+            std::env::var("GITHUB_API_URL")
+                .unwrap_or_else(|_| "https://api.github.com".to_string()),
+            std::env::var("GITHUB_GRAPHQL_API_URL")
+                .unwrap_or_else(|_| "https://api.github.com/graphql".to_string()),
+            std::env::var("GITHUB_RAW_URL")
+                .unwrap_or_else(|_| "https://raw.githubusercontent.com".to_string()),
+        )
     }
 
     pub fn raw(&self) -> &Client {
@@ -1960,10 +1994,7 @@ impl GithubClient {
         branch: &str,
         path: &str,
     ) -> anyhow::Result<Option<Bytes>> {
-        let url = format!(
-            "https://raw.githubusercontent.com/{}/{}/{}",
-            repo, branch, path
-        );
+        let url = format!("{}/{repo}/{branch}/{path}", self.raw_url);
         let req = self.get(&url);
         let req_dbg = format!("{:?}", req);
         let req = req
@@ -2025,8 +2056,8 @@ impl GithubClient {
 
     pub async fn rust_commit(&self, sha: &str) -> Option<GithubCommit> {
         let req = self.get(&format!(
-            "https://api.github.com/repos/rust-lang/rust/commits/{}",
-            sha
+            "{}/repos/rust-lang/rust/commits/{sha}",
+            self.api_url
         ));
         match self.json(req).await {
             Ok(r) => Some(r),
@@ -2039,7 +2070,10 @@ impl GithubClient {
 
     /// This does not retrieve all of them, only the last several.
     pub async fn bors_commits(&self) -> Vec<GithubCommit> {
-        let req = self.get("https://api.github.com/repos/rust-lang/rust/commits?author=bors");
+        let req = self.get(&format!(
+            "{}/repos/rust-lang/rust/commits?author=bors",
+            self.api_url
+        ));
         match self.json(req).await {
             Ok(r) => r,
             Err(e) => {
@@ -2055,13 +2089,10 @@ impl GithubClient {
         query: &str,
         vars: serde_json::Value,
     ) -> anyhow::Result<T> {
-        self.json(
-            self.post(Repository::GITHUB_GRAPHQL_API_URL)
-                .json(&serde_json::json!({
-                    "query": query,
-                    "variables": vars,
-                })),
-        )
+        self.json(self.post(&self.graphql_url).json(&serde_json::json!({
+            "query": query,
+            "variables": vars,
+        })))
         .await
     }
 
@@ -2172,7 +2203,7 @@ impl GithubClient {
     ///
     /// The `full_name` should be something like `rust-lang/rust`.
     pub async fn repository(&self, full_name: &str) -> anyhow::Result<Repository> {
-        let req = self.get(&format!("{}/repos/{full_name}", Repository::GITHUB_API_URL));
+        let req = self.get(&format!("{}/repos/{full_name}", self.api_url));
         self.json(req)
             .await
             .with_context(|| format!("{} failed to get repo", full_name))
@@ -2187,10 +2218,7 @@ impl GithubClient {
         title: &str,
         state: &str,
     ) -> anyhow::Result<Milestone> {
-        let url = format!(
-            "{}/repos/{full_repo_name}/milestones",
-            Repository::GITHUB_API_URL
-        );
+        let url = format!("{}/repos/{full_repo_name}/milestones", self.api_url);
         let resp = self
             .send_req(self.post(&url).json(&serde_json::json!({
                 "title": title,
@@ -2220,7 +2248,7 @@ impl GithubClient {
         loop {
             let url = format!(
                 "{}/repos/{full_repo_name}/milestones?page={page}&state=all",
-                Repository::GITHUB_API_URL
+                self.api_url
             );
             let milestones: Vec<Milestone> = self
                 .json(self.get(&url))
@@ -2243,10 +2271,7 @@ impl GithubClient {
         milestone: &Milestone,
         issue_num: u64,
     ) -> anyhow::Result<()> {
-        let url = format!(
-            "{}/repos/{full_repo_name}/issues/{issue_num}",
-            Repository::GITHUB_API_URL
-        );
+        let url = format!("{}/repos/{full_repo_name}/issues/{issue_num}", self.api_url);
         self.send_req(self.patch(&url).json(&serde_json::json!({
             "milestone": milestone.number
         })))
@@ -2350,7 +2375,7 @@ impl IssuesQuery for LeastRecentlyReviewedPullRequests {
         };
         loop {
             let query = queries::LeastRecentlyReviewedPullRequests::build(args.clone());
-            let req = client.post(Repository::GITHUB_GRAPHQL_API_URL);
+            let req = client.post(&client.graphql_url);
             let req = req.json(&query);
 
             let data: cynic::GraphQlResponse<queries::LeastRecentlyReviewedPullRequests> =
@@ -2485,7 +2510,7 @@ async fn project_items_by_status(
     let mut all_items = vec![];
     loop {
         let query = project_items::Query::build(args.clone());
-        let req = client.post(Repository::GITHUB_GRAPHQL_API_URL);
+        let req = client.post(&client.graphql_url);
         let req = req.json(&query);
 
         let data: cynic::GraphQlResponse<project_items::Query> = client.json(req).await?;

+ 1 - 2
src/handlers/docs_update.rs

@@ -5,7 +5,6 @@ use crate::jobs::Job;
 use anyhow::Context;
 use anyhow::Result;
 use async_trait::async_trait;
-use reqwest::Client;
 use std::fmt::Write;
 
 /// This is the repository where the commits will be created.
@@ -66,7 +65,7 @@ impl Job for DocsUpdateJob {
 }
 
 pub async fn docs_update() -> Result<Option<Issue>> {
-    let gh = GithubClient::new_with_default_token(Client::new());
+    let gh = GithubClient::new_from_env();
     let dest_repo = gh.repository(DEST_REPO).await?;
     let work_repo = gh.repository(WORK_REPO).await?;
 

+ 1 - 3
src/main.rs

@@ -4,7 +4,6 @@ use anyhow::Context as _;
 use futures::future::FutureExt;
 use futures::StreamExt;
 use hyper::{header, Body, Request, Response, Server, StatusCode};
-use reqwest::Client;
 use route_recognizer::Router;
 use std::{env, net::SocketAddr, sync::Arc};
 use tokio::{task, time};
@@ -247,8 +246,7 @@ async fn run_server(addr: SocketAddr) -> anyhow::Result<()> {
         .await
         .context("database migrations")?;
 
-    let client = Client::new();
-    let gh = github::GithubClient::new_with_default_token(client.clone());
+    let gh = github::GithubClient::new_from_env();
     let oc = octocrab::OctocrabBuilder::new()
         .personal_token(github::default_token_from_env())
         .build()

+ 2 - 1
src/team_data.rs

@@ -4,7 +4,8 @@ use rust_team_data::v1::{Teams, ZulipMapping, BASE_URL};
 use serde::de::DeserializeOwned;
 
 async fn by_url<T: DeserializeOwned>(client: &GithubClient, path: &str) -> anyhow::Result<T> {
-    let url = format!("{}{}", BASE_URL, path);
+    let base = std::env::var("TEAMS_API_URL").unwrap_or(BASE_URL.to_string());
+    let url = format!("{}{}", base, path);
     for _ in 0i32..3 {
         let map: Result<T, _> = client.json(client.raw().get(&url)).await;
         match map {