From 886eb8594ce547a2963fba579b38471b8c8311d2 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Wed, 24 Sep 2025 15:16:19 +0200 Subject: [PATCH 1/6] trustpub: Extract `ConfigCreatedEmail` struct --- .../trustpub/github_configs/create/mod.rs | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/controllers/trustpub/github_configs/create/mod.rs b/src/controllers/trustpub/github_configs/create/mod.rs index 72870ec5c1..c4d0b5d30d 100644 --- a/src/controllers/trustpub/github_configs/create/mod.rs +++ b/src/controllers/trustpub/github_configs/create/mod.rs @@ -6,9 +6,9 @@ use crate::email::EmailMessage; use crate::util::errors::{AppResult, bad_request, forbidden, server_error}; use anyhow::Context; use axum::Json; -use crates_io_database::models::OwnerKind; use crates_io_database::models::token::EndpointScope; -use crates_io_database::models::trustpub::NewGitHubConfig; +use crates_io_database::models::trustpub::{GitHubConfig, NewGitHubConfig}; +use crates_io_database::models::{Crate, OwnerKind, User}; use crates_io_database::schema::{crate_owners, emails, users}; use crates_io_github::GitHubError; use crates_io_trustpub::github::validation::{ @@ -17,7 +17,6 @@ use crates_io_trustpub::github::validation::{ use diesel::prelude::*; use diesel_async::RunQueryDsl; use http::request::Parts; -use minijinja::context; use tracing::warn; #[cfg(test)] @@ -118,7 +117,12 @@ pub async fn create_trustpub_github_config( .collect::>(); for (recipient, email_address) in &recipients { - let context = context! { recipient, auth_user, krate, saved_config }; + let context = ConfigCreatedEmail { + recipient, + auth_user, + krate: &krate, + saved_config: &saved_config, + }; if let Err(err) = send_notification_email(&state, email_address, context).await { warn!("Failed to send trusted publishing notification to {email_address}: {err}"); @@ -139,13 +143,27 @@ pub async fn create_trustpub_github_config( Ok(Json(json::CreateResponse { github_config })) } +#[derive(serde::Serialize)] +struct ConfigCreatedEmail<'a> { + recipient: &'a str, + auth_user: &'a User, + krate: &'a Crate, + saved_config: &'a GitHubConfig, +} + +impl ConfigCreatedEmail<'_> { + fn render(&self) -> Result { + EmailMessage::from_template("trustpub_config_created", self) + } +} + async fn send_notification_email( state: &AppState, email_address: &str, - context: minijinja::Value, + context: ConfigCreatedEmail<'_>, ) -> anyhow::Result<()> { - let email = EmailMessage::from_template("trustpub_config_created", context) - .context("Failed to render email template")?; + let email = context.render(); + let email = email.context("Failed to render email template")?; state .emails From 9111311c1a203d1c3234f8330fd5006ea8eb2eff Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Wed, 24 Sep 2025 15:44:39 +0200 Subject: [PATCH 2/6] trustpub: Extract `ConfigDeletedEmail` struct --- .../trustpub/github_configs/delete/mod.rs | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/controllers/trustpub/github_configs/delete/mod.rs b/src/controllers/trustpub/github_configs/delete/mod.rs index 662d72ecbe..4dc21cfef1 100644 --- a/src/controllers/trustpub/github_configs/delete/mod.rs +++ b/src/controllers/trustpub/github_configs/delete/mod.rs @@ -6,13 +6,12 @@ use anyhow::Context; use axum::extract::Path; use crates_io_database::models::token::EndpointScope; use crates_io_database::models::trustpub::GitHubConfig; -use crates_io_database::models::{Crate, OwnerKind}; +use crates_io_database::models::{Crate, OwnerKind, User}; use crates_io_database::schema::{crate_owners, crates, emails, trustpub_configs_github, users}; use diesel::prelude::*; use diesel_async::RunQueryDsl; use http::StatusCode; use http::request::Parts; -use minijinja::context; use tracing::warn; #[cfg(test)] @@ -83,7 +82,12 @@ pub async fn delete_trustpub_github_config( .collect::>(); for (recipient, email_address) in &recipients { - let context = context! { recipient, auth_user, krate, config }; + let context = ConfigDeletedEmail { + recipient, + auth_user, + krate: &krate, + config: &config, + }; if let Err(err) = send_notification_email(&state, email_address, context).await { warn!("Failed to send trusted publishing notification to {email_address}: {err}"); @@ -93,13 +97,27 @@ pub async fn delete_trustpub_github_config( Ok(StatusCode::NO_CONTENT) } +#[derive(serde::Serialize)] +struct ConfigDeletedEmail<'a> { + recipient: &'a str, + auth_user: &'a User, + krate: &'a Crate, + config: &'a GitHubConfig, +} + +impl ConfigDeletedEmail<'_> { + fn render(&self) -> Result { + EmailMessage::from_template("trustpub_config_deleted", self) + } +} + async fn send_notification_email( state: &AppState, email_address: &str, - context: minijinja::Value, + context: ConfigDeletedEmail<'_>, ) -> anyhow::Result<()> { - let email = EmailMessage::from_template("trustpub_config_deleted", context) - .context("Failed to render email template")?; + let email = context.render(); + let email = email.context("Failed to render email template")?; state .emails From 4131fe9dcf0f77a5dfca193618085a61ef5ee241 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Wed, 24 Sep 2025 15:47:11 +0200 Subject: [PATCH 3/6] trustpub: Move `ConfigEmail` structs to dedicated module --- src/controllers/trustpub/emails.rs | 31 +++++++++++++++++++ .../trustpub/github_configs/create/mod.rs | 20 ++---------- .../trustpub/github_configs/delete/mod.rs | 18 ++--------- src/controllers/trustpub/mod.rs | 1 + 4 files changed, 37 insertions(+), 33 deletions(-) create mode 100644 src/controllers/trustpub/emails.rs diff --git a/src/controllers/trustpub/emails.rs b/src/controllers/trustpub/emails.rs new file mode 100644 index 0000000000..d565c6edaf --- /dev/null +++ b/src/controllers/trustpub/emails.rs @@ -0,0 +1,31 @@ +use crate::email::EmailMessage; +use crates_io_database::models::trustpub::GitHubConfig; +use crates_io_database::models::{Crate, User}; + +#[derive(serde::Serialize)] +pub struct ConfigCreatedEmail<'a> { + pub recipient: &'a str, + pub auth_user: &'a User, + pub krate: &'a Crate, + pub saved_config: &'a GitHubConfig, +} + +impl ConfigCreatedEmail<'_> { + pub fn render(&self) -> Result { + EmailMessage::from_template("trustpub_config_created", self) + } +} + +#[derive(serde::Serialize)] +pub struct ConfigDeletedEmail<'a> { + pub recipient: &'a str, + pub auth_user: &'a User, + pub krate: &'a Crate, + pub config: &'a GitHubConfig, +} + +impl ConfigDeletedEmail<'_> { + pub fn render(&self) -> Result { + EmailMessage::from_template("trustpub_config_deleted", self) + } +} diff --git a/src/controllers/trustpub/github_configs/create/mod.rs b/src/controllers/trustpub/github_configs/create/mod.rs index c4d0b5d30d..4ef98783ee 100644 --- a/src/controllers/trustpub/github_configs/create/mod.rs +++ b/src/controllers/trustpub/github_configs/create/mod.rs @@ -1,14 +1,14 @@ use crate::app::AppState; use crate::auth::AuthCheck; use crate::controllers::krate::load_crate; +use crate::controllers::trustpub::emails::ConfigCreatedEmail; use crate::controllers::trustpub::github_configs::json; -use crate::email::EmailMessage; use crate::util::errors::{AppResult, bad_request, forbidden, server_error}; use anyhow::Context; use axum::Json; +use crates_io_database::models::OwnerKind; use crates_io_database::models::token::EndpointScope; -use crates_io_database::models::trustpub::{GitHubConfig, NewGitHubConfig}; -use crates_io_database::models::{Crate, OwnerKind, User}; +use crates_io_database::models::trustpub::NewGitHubConfig; use crates_io_database::schema::{crate_owners, emails, users}; use crates_io_github::GitHubError; use crates_io_trustpub::github::validation::{ @@ -143,20 +143,6 @@ pub async fn create_trustpub_github_config( Ok(Json(json::CreateResponse { github_config })) } -#[derive(serde::Serialize)] -struct ConfigCreatedEmail<'a> { - recipient: &'a str, - auth_user: &'a User, - krate: &'a Crate, - saved_config: &'a GitHubConfig, -} - -impl ConfigCreatedEmail<'_> { - fn render(&self) -> Result { - EmailMessage::from_template("trustpub_config_created", self) - } -} - async fn send_notification_email( state: &AppState, email_address: &str, diff --git a/src/controllers/trustpub/github_configs/delete/mod.rs b/src/controllers/trustpub/github_configs/delete/mod.rs index 4dc21cfef1..aaacf67fca 100644 --- a/src/controllers/trustpub/github_configs/delete/mod.rs +++ b/src/controllers/trustpub/github_configs/delete/mod.rs @@ -1,12 +1,12 @@ use crate::app::AppState; use crate::auth::AuthCheck; -use crate::email::EmailMessage; +use crate::controllers::trustpub::emails::ConfigDeletedEmail; use crate::util::errors::{AppResult, bad_request, not_found}; use anyhow::Context; use axum::extract::Path; use crates_io_database::models::token::EndpointScope; use crates_io_database::models::trustpub::GitHubConfig; -use crates_io_database::models::{Crate, OwnerKind, User}; +use crates_io_database::models::{Crate, OwnerKind}; use crates_io_database::schema::{crate_owners, crates, emails, trustpub_configs_github, users}; use diesel::prelude::*; use diesel_async::RunQueryDsl; @@ -97,20 +97,6 @@ pub async fn delete_trustpub_github_config( Ok(StatusCode::NO_CONTENT) } -#[derive(serde::Serialize)] -struct ConfigDeletedEmail<'a> { - recipient: &'a str, - auth_user: &'a User, - krate: &'a Crate, - config: &'a GitHubConfig, -} - -impl ConfigDeletedEmail<'_> { - fn render(&self) -> Result { - EmailMessage::from_template("trustpub_config_deleted", self) - } -} - async fn send_notification_email( state: &AppState, email_address: &str, diff --git a/src/controllers/trustpub/mod.rs b/src/controllers/trustpub/mod.rs index fe0f81d16f..461bd02043 100644 --- a/src/controllers/trustpub/mod.rs +++ b/src/controllers/trustpub/mod.rs @@ -1,2 +1,3 @@ +pub mod emails; pub mod github_configs; pub mod tokens; From 2bc11867540642c78d15b6ccf02f4ee4d2e322f2 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Wed, 24 Sep 2025 15:56:31 +0200 Subject: [PATCH 4/6] trustpub/emails: Add doc comments --- src/controllers/trustpub/emails.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/controllers/trustpub/emails.rs b/src/controllers/trustpub/emails.rs index d565c6edaf..06867fdfd7 100644 --- a/src/controllers/trustpub/emails.rs +++ b/src/controllers/trustpub/emails.rs @@ -4,9 +4,13 @@ use crates_io_database::models::{Crate, User}; #[derive(serde::Serialize)] pub struct ConfigCreatedEmail<'a> { + /// The GitHub login of the email recipient. pub recipient: &'a str, + /// The user who created the trusted publishing configuration. pub auth_user: &'a User, + /// The crate for which the trusted publishing configuration was created. pub krate: &'a Crate, + /// The trusted publishing configuration that was created. pub saved_config: &'a GitHubConfig, } @@ -18,9 +22,13 @@ impl ConfigCreatedEmail<'_> { #[derive(serde::Serialize)] pub struct ConfigDeletedEmail<'a> { + /// The GitHub login of the email recipient. pub recipient: &'a str, + /// The user who deleted the trusted publishing configuration. pub auth_user: &'a User, + /// The crate for which the trusted publishing configuration was deleted. pub krate: &'a Crate, + /// The trusted publishing configuration that was deleted. pub config: &'a GitHubConfig, } From 831586a9100ae77fa6aa3a21d76d2358dd779f65 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Wed, 24 Sep 2025 16:15:52 +0200 Subject: [PATCH 5/6] database/models/krate: Make `max_upload_size` pub There is no reason why this one should be private when the rest are also public... --- crates/crates_io_database/src/models/krate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/crates_io_database/src/models/krate.rs b/crates/crates_io_database/src/models/krate.rs index 87521cd8c2..23988ec4d2 100644 --- a/crates/crates_io_database/src/models/krate.rs +++ b/crates/crates_io_database/src/models/krate.rs @@ -35,7 +35,7 @@ pub struct Crate { pub homepage: Option, pub documentation: Option, pub repository: Option, - max_upload_size: Option, + pub max_upload_size: Option, pub max_features: Option, } From 9a9bd1736f97e4d8851fd69e11db7e95c7d7c0ee Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Wed, 24 Sep 2025 16:22:24 +0200 Subject: [PATCH 6/6] trustpub/emails: Add rendering tests --- src/controllers/trustpub/emails.rs | 135 ++++++++++++++++++ ...emails__tests__config_created_email-2.snap | 21 +++ ...g_created_email_different_recipient-2.snap | 21 +++ ...nfig_created_email_with_environment-2.snap | 21 +++ ...emails__tests__config_deleted_email-2.snap | 19 +++ ...g_deleted_email_different_recipient-2.snap | 19 +++ ...nfig_deleted_email_with_environment-2.snap | 19 +++ 7 files changed, 255 insertions(+) create mode 100644 src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email-2.snap create mode 100644 src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_different_recipient-2.snap create mode 100644 src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_with_environment-2.snap create mode 100644 src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email-2.snap create mode 100644 src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_different_recipient-2.snap create mode 100644 src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_with_environment-2.snap diff --git a/src/controllers/trustpub/emails.rs b/src/controllers/trustpub/emails.rs index 06867fdfd7..e6e45d885e 100644 --- a/src/controllers/trustpub/emails.rs +++ b/src/controllers/trustpub/emails.rs @@ -37,3 +37,138 @@ impl ConfigDeletedEmail<'_> { EmailMessage::from_template("trustpub_config_deleted", self) } } + +#[cfg(test)] +mod tests { + use super::*; + use chrono::Utc; + use claims::assert_ok; + use insta::assert_snapshot; + + fn test_user() -> User { + User { + id: 1, + gh_login: "octocat".into(), + name: Some("The Octocat".into()), + gh_id: 123, + gh_avatar: None, + gh_encrypted_token: vec![], + account_lock_reason: None, + account_lock_until: None, + is_admin: false, + publish_notifications: true, + } + } + + fn test_crate() -> Crate { + Crate { + id: 1, + name: "my-crate".into(), + updated_at: Utc::now(), + created_at: Utc::now(), + description: None, + homepage: None, + documentation: None, + repository: None, + max_upload_size: None, + max_features: None, + } + } + + fn test_github_config(environment: Option<&str>) -> GitHubConfig { + GitHubConfig { + id: 1, + created_at: Utc::now(), + crate_id: 1, + repository_owner_id: 42, + repository_owner: "rust-lang".into(), + repository_name: "rust".into(), + workflow_filename: "publish.yml".into(), + environment: environment.map(String::from), + } + } + + #[test] + fn test_config_created_email() { + let email = ConfigCreatedEmail { + recipient: "octocat", + auth_user: &test_user(), + krate: &test_crate(), + saved_config: &test_github_config(None), + }; + + let rendered = assert_ok!(email.render()); + assert_snapshot!(rendered.subject, @"crates.io: Trusted Publishing configuration added to my-crate"); + assert_snapshot!(rendered.body_text); + } + + #[test] + fn test_config_created_email_with_environment() { + let email = ConfigCreatedEmail { + recipient: "octocat", + auth_user: &test_user(), + krate: &test_crate(), + saved_config: &test_github_config(Some("production")), + }; + + let rendered = assert_ok!(email.render()); + assert_snapshot!(rendered.subject, @"crates.io: Trusted Publishing configuration added to my-crate"); + assert_snapshot!(rendered.body_text); + } + + #[test] + fn test_config_created_email_different_recipient() { + let email = ConfigCreatedEmail { + recipient: "team-member", + auth_user: &test_user(), + krate: &test_crate(), + saved_config: &test_github_config(None), + }; + + let rendered = assert_ok!(email.render()); + assert_snapshot!(rendered.subject, @"crates.io: Trusted Publishing configuration added to my-crate"); + assert_snapshot!(rendered.body_text); + } + + #[test] + fn test_config_deleted_email() { + let email = ConfigDeletedEmail { + recipient: "octocat", + auth_user: &test_user(), + krate: &test_crate(), + config: &test_github_config(None), + }; + + let rendered = assert_ok!(email.render()); + assert_snapshot!(rendered.subject, @"crates.io: Trusted Publishing configuration removed from my-crate"); + assert_snapshot!(rendered.body_text); + } + + #[test] + fn test_config_deleted_email_with_environment() { + let email = ConfigDeletedEmail { + recipient: "octocat", + auth_user: &test_user(), + krate: &test_crate(), + config: &test_github_config(Some("production")), + }; + + let rendered = assert_ok!(email.render()); + assert_snapshot!(rendered.subject, @"crates.io: Trusted Publishing configuration removed from my-crate"); + assert_snapshot!(rendered.body_text); + } + + #[test] + fn test_config_deleted_email_different_recipient() { + let email = ConfigDeletedEmail { + recipient: "team-member", + auth_user: &test_user(), + krate: &test_crate(), + config: &test_github_config(None), + }; + + let rendered = assert_ok!(email.render()); + assert_snapshot!(rendered.subject, @"crates.io: Trusted Publishing configuration removed from my-crate"); + assert_snapshot!(rendered.body_text); + } +} diff --git a/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email-2.snap b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email-2.snap new file mode 100644 index 0000000000..5ca814a332 --- /dev/null +++ b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email-2.snap @@ -0,0 +1,21 @@ +--- +source: src/controllers/trustpub/emails.rs +expression: rendered.body_text +--- +Hello octocat! + +You added a new "Trusted Publishing" configuration for GitHub Actions to your crate "my-crate". Trusted publishers act as trusted users and can publish new versions of the crate automatically. + +Trusted Publishing configuration: + +- Repository owner: rust-lang +- Repository name: rust +- Workflow filename: publish.yml +- Environment: (not set) + +If you did not make this change and you think it was made maliciously, you can remove the configuration from the crate via the "Settings" tab on the crate's page. + +If you are unable to revert the change and need to do so, you can email help@crates.io for assistance. + +-- +The crates.io Team diff --git a/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_different_recipient-2.snap b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_different_recipient-2.snap new file mode 100644 index 0000000000..4231f88f3f --- /dev/null +++ b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_different_recipient-2.snap @@ -0,0 +1,21 @@ +--- +source: src/controllers/trustpub/emails.rs +expression: rendered.body_text +--- +Hello team-member! + +crates.io user octocat added a new "Trusted Publishing" configuration for GitHub Actions to a crate that you manage ("my-crate"). Trusted publishers act as trusted users and can publish new versions of the crate automatically. + +Trusted Publishing configuration: + +- Repository owner: rust-lang +- Repository name: rust +- Workflow filename: publish.yml +- Environment: (not set) + +If you did not make this change and you think it was made maliciously, you can remove the configuration from the crate via the "Settings" tab on the crate's page. + +If you are unable to revert the change and need to do so, you can email help@crates.io for assistance. + +-- +The crates.io Team diff --git a/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_with_environment-2.snap b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_with_environment-2.snap new file mode 100644 index 0000000000..223f897258 --- /dev/null +++ b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_with_environment-2.snap @@ -0,0 +1,21 @@ +--- +source: src/controllers/trustpub/emails.rs +expression: rendered.body_text +--- +Hello octocat! + +You added a new "Trusted Publishing" configuration for GitHub Actions to your crate "my-crate". Trusted publishers act as trusted users and can publish new versions of the crate automatically. + +Trusted Publishing configuration: + +- Repository owner: rust-lang +- Repository name: rust +- Workflow filename: publish.yml +- Environment: production + +If you did not make this change and you think it was made maliciously, you can remove the configuration from the crate via the "Settings" tab on the crate's page. + +If you are unable to revert the change and need to do so, you can email help@crates.io for assistance. + +-- +The crates.io Team diff --git a/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email-2.snap b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email-2.snap new file mode 100644 index 0000000000..488aca92de --- /dev/null +++ b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email-2.snap @@ -0,0 +1,19 @@ +--- +source: src/controllers/trustpub/emails.rs +expression: rendered.body_text +--- +Hello octocat! + +You removed a "Trusted Publishing" configuration for GitHub Actions from your crate "my-crate". + +Trusted Publishing configuration: + +- Repository owner: rust-lang +- Repository name: rust +- Workflow filename: publish.yml +- Environment: (not set) + +If you did not make this change and you think it was made maliciously, you can email help@crates.io for assistance. + +-- +The crates.io Team diff --git a/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_different_recipient-2.snap b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_different_recipient-2.snap new file mode 100644 index 0000000000..5f806113d5 --- /dev/null +++ b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_different_recipient-2.snap @@ -0,0 +1,19 @@ +--- +source: src/controllers/trustpub/emails.rs +expression: rendered.body_text +--- +Hello team-member! + +crates.io user octocat removed a "Trusted Publishing" configuration for GitHub Actions from a crate that you manage ("my-crate"). + +Trusted Publishing configuration: + +- Repository owner: rust-lang +- Repository name: rust +- Workflow filename: publish.yml +- Environment: (not set) + +If you did not make this change and you think it was made maliciously, you can email help@crates.io for assistance. + +-- +The crates.io Team diff --git a/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_with_environment-2.snap b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_with_environment-2.snap new file mode 100644 index 0000000000..54bebd6d24 --- /dev/null +++ b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_with_environment-2.snap @@ -0,0 +1,19 @@ +--- +source: src/controllers/trustpub/emails.rs +expression: rendered.body_text +--- +Hello octocat! + +You removed a "Trusted Publishing" configuration for GitHub Actions from your crate "my-crate". + +Trusted Publishing configuration: + +- Repository owner: rust-lang +- Repository name: rust +- Workflow filename: publish.yml +- Environment: production + +If you did not make this change and you think it was made maliciously, you can email help@crates.io for assistance. + +-- +The crates.io Team