Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions crates/cargo-test-support/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,10 @@ type RequestCallback = Box<dyn Send + Fn(&Request, &HttpServer) -> Response>;
pub struct RegistryBuilder {
/// If set, configures an alternate registry with the given name.
alternative: Option<String>,
/// The authorization token for the registry.
/// The client-supplied authorization token for the registry.
token: Option<Token>,
/// The actual server authorization token for the registry.
server_token: Option<Token>,
/// If set, the registry requires authorization for all operations.
auth_required: bool,
/// If set, serves the index over http.
Expand Down Expand Up @@ -241,6 +243,7 @@ impl RegistryBuilder {
RegistryBuilder {
alternative: None,
token: None,
server_token: None,
auth_required: false,
http_api: false,
http_index: false,
Expand Down Expand Up @@ -309,13 +312,20 @@ impl RegistryBuilder {
self
}

/// Sets the token value
/// Sets the client-supplied token value
#[must_use]
pub fn token(mut self, token: Token) -> Self {
self.token = Some(token);
self
}

/// Sets the server token value
#[must_use]
pub fn server_token(mut self, token: Token) -> Self {
self.server_token = Some(token);
self
}

/// Sets this registry to require the authentication token for
/// all operations.
#[must_use]
Expand Down Expand Up @@ -372,6 +382,9 @@ impl RegistryBuilder {
.token
.unwrap_or_else(|| Token::Plaintext(format!("{prefix}sekrit")));

// Uses the client token unless otherwise set.
let server_token = self.server_token.unwrap_or_else(|| token.clone());

let (server, index_url, api_url, dl_url) = if !self.http_index && !self.http_api {
// No need to start the HTTP server.
(None, index_url, api_url, dl_url)
Expand All @@ -380,7 +393,7 @@ impl RegistryBuilder {
registry_path.clone(),
dl_path,
api_path.clone(),
token.clone(),
server_token.clone(),
self.auth_required,
self.custom_responders,
self.not_found_handler,
Expand Down
195 changes: 195 additions & 0 deletions tests/testsuite/alt_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use cargo_test_support::publish::validate_alt_upload;
use cargo_test_support::registry::{self, Package, RegistryBuilder};
use cargo_test_support::str;
use cargo_test_support::{basic_manifest, paths, project};
use rand::Rng;

#[cargo_test]
fn depend_on_alt_registry() {
Expand Down Expand Up @@ -1985,3 +1986,197 @@ fn empty_dependency_registry() {
"#]])
.run();
}

enum TokenStatus {
Valid,
Missing,
InvalidHasScheme,
InvalidNoScheme,
}

struct TokenTest {
crates_io_token: TokenStatus,
alternative_token: TokenStatus,
expected_message: &'static str,
}

fn token_test(token_test: TokenTest) {
let server_token = rand::rng()
.sample_iter(&rand::distr::Alphanumeric)
.take(8)
.map(char::from)
.collect::<String>();

let crates_io_builder = RegistryBuilder::new()
.http_api()
.http_index()
.credential_provider(&[&"cargo:token"])
.auth_required();

let alternative_builder = RegistryBuilder::new()
.http_api()
.http_index()
.credential_provider(&[&"cargo:token"])
.alternative()
.auth_required();

let crates_io_mock = match token_test.crates_io_token {
TokenStatus::Valid => crates_io_builder.build(),
TokenStatus::Missing => crates_io_builder.no_configure_token().build(),
TokenStatus::InvalidHasScheme => crates_io_builder
.token(cargo_test_support::registry::Token::Plaintext(
"Bearer <TOKEN>".to_string(),
))
.server_token(cargo_test_support::registry::Token::Plaintext(
server_token.clone(),
))
.build(),
TokenStatus::InvalidNoScheme => crates_io_builder
.token(cargo_test_support::registry::Token::Plaintext(
"<TOKEN>".to_string(),
))
.server_token(cargo_test_support::registry::Token::Plaintext(
server_token.clone(),
))
.build(),
};
let _alternative_mock = match token_test.alternative_token {
TokenStatus::Valid => alternative_builder.build(),
TokenStatus::Missing => alternative_builder.no_configure_token().build(),
TokenStatus::InvalidHasScheme => alternative_builder
.token(cargo_test_support::registry::Token::Plaintext(
"Bearer <TOKEN>".to_string(),
))
.server_token(cargo_test_support::registry::Token::Plaintext(
server_token.clone(),
))
.build(),
TokenStatus::InvalidNoScheme => alternative_builder
.token(cargo_test_support::registry::Token::Plaintext(
"<TOKEN>".to_string(),
))
.server_token(cargo_test_support::registry::Token::Plaintext(
server_token.clone(),
))
.build(),
};

let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
edition = "2015"

[dependencies.bar_alt]
version = "0.0.1"
registry = "alternative"

[dependencies.bar_crates]
version = "0.0.1"
"#,
)
.file("src/main.rs", "fn main() {}")
.build();

Package::new("bar_alt", "0.0.1").alternative(true).publish();
Package::new("bar_crates", "0.0.1").publish();
p.cargo("build")
.replace_crates_io(crates_io_mock.index_url())
.with_status(101)
.with_stderr_contains(token_test.expected_message)
.run();
}

macro_rules! token_error_messages {
($($name:ident: $value:expr)*) => {
$(
#[cargo_test]
fn $name() {
token_test($value);
}
)*
}
}

token_error_messages! {
Copy link
Author

@nathanhammond nathanhammond Sep 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@epage: since this whole PR is basically test cases followed by a tiny bit of string manipulation, would you mind taking an incremental look at just the test commit?

(the tests pass, auth_required on crates.io I've not yet attempted to discover if publish --workspace can support both crates.io and custom registries simultaneously. Regardless, I started with the cross-product to fully enumerate behavior.)

// Skips crates_valid_alt_valid because it would not error.
crates_valid_alt_missing: TokenTest {
crates_io_token: TokenStatus::Valid,
alternative_token: TokenStatus::Missing,
expected_message: " no token found for `alternative`, please run `cargo login --registry alternative`\n or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN",
}
crates_valid_alt_invalid_has_scheme: TokenTest {
crates_io_token: TokenStatus::Valid,
alternative_token: TokenStatus::InvalidHasScheme,
expected_message: " token rejected for `alternative`, please run `cargo login --registry alternative`\n or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN",
}
crates_valid_alt_invalid_no_scheme: TokenTest {
crates_io_token: TokenStatus::Valid,
alternative_token: TokenStatus::InvalidNoScheme,
expected_message: " token rejected for `alternative`, please run `cargo login --registry alternative`\n or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN",
}
crates_missing_alt_valid: TokenTest {
crates_io_token: TokenStatus::Missing,
alternative_token: TokenStatus::Valid,
expected_message: " no token found, please run `cargo login`\n or use environment variable CARGO_REGISTRY_TOKEN",
}
crates_missing_alt_missing: TokenTest {
crates_io_token: TokenStatus::Missing,
alternative_token: TokenStatus::Missing,
expected_message: " no token found for `alternative`, please run `cargo login --registry alternative`\n or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN",
}
crates_missing_alt_invalid_has_scheme: TokenTest {
crates_io_token: TokenStatus::Missing,
alternative_token: TokenStatus::InvalidHasScheme,
expected_message: " no token found, please run `cargo login`\n or use environment variable CARGO_REGISTRY_TOKEN",
}
crates_missing_alt_invalid_no_scheme: TokenTest {
crates_io_token: TokenStatus::Missing,
alternative_token: TokenStatus::InvalidNoScheme,
expected_message: " no token found, please run `cargo login`\n or use environment variable CARGO_REGISTRY_TOKEN",
}
crates_invalid_has_scheme_alt_valid: TokenTest {
crates_io_token: TokenStatus::Missing,
alternative_token: TokenStatus::Valid,
expected_message: " no token found, please run `cargo login`\n or use environment variable CARGO_REGISTRY_TOKEN",
}
crates_invalid_has_scheme_alt_missing: TokenTest {
crates_io_token: TokenStatus::Missing,
alternative_token: TokenStatus::Missing,
expected_message: " no token found for `alternative`, please run `cargo login --registry alternative`\n or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN",
}
crates_invalid_has_scheme_alt_invalid_has_scheme: TokenTest {
crates_io_token: TokenStatus::Missing,
alternative_token: TokenStatus::InvalidHasScheme,
expected_message: " no token found, please run `cargo login`\n or use environment variable CARGO_REGISTRY_TOKEN",
}
crates_invalid_has_scheme_alt_invalid_no_scheme: TokenTest {
crates_io_token: TokenStatus::Missing,
alternative_token: TokenStatus::InvalidNoScheme,
expected_message: " no token found, please run `cargo login`\n or use environment variable CARGO_REGISTRY_TOKEN",
}
crates_invalid_no_scheme_alt_valid: TokenTest {
crates_io_token: TokenStatus::Missing,
alternative_token: TokenStatus::Valid,
expected_message: " no token found, please run `cargo login`\n or use environment variable CARGO_REGISTRY_TOKEN",
}
crates_invalid_no_scheme_alt_missing: TokenTest {
crates_io_token: TokenStatus::Missing,
alternative_token: TokenStatus::Missing,
expected_message: " no token found for `alternative`, please run `cargo login --registry alternative`\n or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN",
}
crates_invalid_no_scheme_alt_invalid_has_scheme: TokenTest {
crates_io_token: TokenStatus::Missing,
alternative_token: TokenStatus::InvalidHasScheme,
expected_message: " no token found, please run `cargo login`\n or use environment variable CARGO_REGISTRY_TOKEN",
}
crates_invalid_no_scheme_alt_invalid_no_scheme: TokenTest {
crates_io_token: TokenStatus::Missing,
alternative_token: TokenStatus::InvalidNoScheme,
expected_message: " no token found, please run `cargo login`\n or use environment variable CARGO_REGISTRY_TOKEN",
}
}