Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions crates/server/src/services/mcp_credential_repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ pub async fn delete_user_credential(
sqlx::query(
r#"UPDATE mcp_user_credentials
SET is_default = true
WHERE id = (
SELECT id FROM mcp_user_credentials
WHERE mcp_server_id = $1 AND user_id = $2 AND account_label = (
SELECT account_label FROM mcp_user_credentials
WHERE mcp_server_id = $1 AND user_id = $2
ORDER BY created_at DESC NULLS LAST
LIMIT 1
Expand Down
71 changes: 71 additions & 0 deletions crates/test-support/tests/admin_mcp_catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,77 @@ async fn accounts_are_listed_switched_and_revoked() {
.assert_status(404);
}

#[ignore = "integration test — run via `make test-it`"]
#[tokio::test]
async fn revoking_the_default_account_promotes_the_newest_one() {
let app = TestApp::spawn().await;
let con = admin_session(&app).await;
let upstream = mcp_ok().await;
let id = fixtures::create_mcp_server_with(
&app.db,
&unique_name("promote"),
&prefix(),
&format!("{}/mcp", upstream.uri()),
fixtures::McpServerOpts {
auth_shape: "static".into(),
..Default::default()
},
)
.await
.unwrap();
for label in ["first", "second", "third"] {
con.put(
&format!("/api/mcp/connections/{id}/{label}/static-token"),
json!({"token": format!("tok-{label}")}),
)
.await
.unwrap()
.assert_ok();
// created_at decides who is promoted; keep them apart.
tokio::time::sleep(Duration::from_millis(20)).await;
}
let defaults = || async {
let rows: Vec<(String, bool)> = sqlx::query_as(
"SELECT account_label, is_default FROM mcp_user_credentials
WHERE mcp_server_id = $1 ORDER BY account_label",
)
.bind(id)
.fetch_all(&app.db)
.await
.unwrap();
rows
};
assert_eq!(
defaults().await,
vec![
("first".to_string(), true),
("second".to_string(), false),
("third".to_string(), false)
]
);

con.delete(&format!("/api/mcp/connections/{id}/first"))
.await
.unwrap()
.assert_ok();
assert_eq!(
defaults().await,
vec![("second".to_string(), false), ("third".to_string(), true)]
);

// The last account goes too; nothing is left to promote.
con.delete(&format!("/api/mcp/connections/{id}/third"))
.await
.unwrap()
.assert_ok();
assert_eq!(defaults().await, vec![("second".to_string(), true)]);
con.delete(&format!("/api/mcp/connections/{id}/second"))
.await
.unwrap()
.assert_ok();
assert_eq!(defaults().await, vec![]);
}

#[ignore = "integration test — run via `make test-it`"]
#[tokio::test]
async fn connections_list_only_per_user_servers_that_need_a_credential() {
Expand Down
Loading