Skip to content

Commit 92afc2d

Browse files
author
Mohammod Al Amin Ashik
committed
Complete P2: Integration tests (29 integration + enhanced fixtures)
1 parent f412304 commit 92afc2d

4 files changed

Lines changed: 200 additions & 1 deletion

File tree

tests/rust/src/lib.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
//! Shared test utilities and fixtures for McpMux integration tests.
22
3-
pub use mcpmux_core::domain::{InstalledServer, Space};
3+
pub use mcpmux_core::domain::{InstalledServer, Space, FeatureSet, FeatureSetType};
44

55
/// Test fixture utilities
66
pub mod fixtures {
77
use super::*;
8+
use uuid::Uuid;
89

910
/// Create a test space with default values
1011
pub fn test_space(name: &str) -> Space {
@@ -25,6 +26,33 @@ pub mod fixtures {
2526
InstalledServer::new(space_id, server_id)
2627
.with_enabled(true)
2728
}
29+
30+
/// Create a test feature set
31+
pub fn test_feature_set(name: &str, space_id: &str) -> FeatureSet {
32+
FeatureSet::new_custom(name, space_id)
33+
.with_icon("🔧")
34+
.with_description(format!("Test feature set: {}", name))
35+
}
36+
37+
/// Create an "all features" feature set
38+
pub fn all_features_set(space_id: &str) -> FeatureSet {
39+
FeatureSet::new_all(space_id)
40+
}
41+
42+
/// Create a "default" feature set
43+
pub fn default_feature_set(space_id: &str) -> FeatureSet {
44+
FeatureSet::new_default(space_id)
45+
}
46+
47+
/// Create a server-all feature set
48+
pub fn server_all_feature_set(space_id: &str, server_id: &str, server_name: &str) -> FeatureSet {
49+
FeatureSet::new_server_all(space_id, server_id, server_name)
50+
}
51+
52+
/// Generate a random UUID string
53+
pub fn random_id() -> String {
54+
Uuid::new_v4().to_string()
55+
}
2856
}
2957

3058
/// Database test helpers

tests/rust/tests/database/repositories.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,62 @@ async fn test_space_repository_set_default() {
145145
let space1_reloaded = SpaceRepository::get(&repo, &space1_id).await.unwrap().unwrap();
146146
assert!(!space1_reloaded.is_default);
147147
}
148+
149+
#[tokio::test]
150+
async fn test_space_repository_concurrent_reads() {
151+
let test_db = TestDatabase::new();
152+
let db = Arc::new(Mutex::new(test_db.db));
153+
let repo = Arc::new(SqliteSpaceRepository::new(db));
154+
155+
// Create a space
156+
let space = fixtures::test_space("Concurrent Test");
157+
let space_id = space.id.clone();
158+
SpaceRepository::create(repo.as_ref(), &space).await.unwrap();
159+
160+
// Spawn multiple concurrent reads
161+
let mut handles = vec![];
162+
for _ in 0..5 {
163+
let repo_clone = Arc::clone(&repo);
164+
let id = space_id.clone();
165+
handles.push(tokio::spawn(async move {
166+
SpaceRepository::get(repo_clone.as_ref(), &id).await
167+
}));
168+
}
169+
170+
// All reads should succeed
171+
for handle in handles {
172+
let result = handle.await.expect("Task panicked");
173+
assert!(result.is_ok());
174+
assert!(result.unwrap().is_some());
175+
}
176+
}
177+
178+
#[tokio::test]
179+
async fn test_space_repository_concurrent_writes() {
180+
let test_db = TestDatabase::new();
181+
let db = Arc::new(Mutex::new(test_db.db));
182+
let repo = Arc::new(SqliteSpaceRepository::new(db));
183+
184+
// Spawn multiple concurrent creates
185+
let mut handles = vec![];
186+
for i in 0..5 {
187+
let repo_clone = Arc::clone(&repo);
188+
handles.push(tokio::spawn(async move {
189+
let space = fixtures::test_space(&format!("Concurrent Space {}", i));
190+
SpaceRepository::create(repo_clone.as_ref(), &space).await
191+
}));
192+
}
193+
194+
// All writes should succeed
195+
for handle in handles {
196+
let result = handle.await.expect("Task panicked");
197+
assert!(result.is_ok());
198+
}
199+
200+
// Verify all spaces were created
201+
let all_spaces = SpaceRepository::list(repo.as_ref()).await.unwrap();
202+
let concurrent_count = all_spaces.iter()
203+
.filter(|s| s.name.starts_with("Concurrent Space"))
204+
.count();
205+
assert_eq!(concurrent_count, 5);
206+
}

tests/rust/tests/security/jwt.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//! JWT integration tests
2+
//!
3+
//! Tests for token creation and validation using mcpmux-gateway auth module.
4+
5+
use mcpmux_gateway::auth::{create_access_token, create_refresh_token, validate_token};
6+
7+
const TEST_SECRET: &[u8] = b"test_secret_key_that_is_32_bytes";
8+
9+
#[test]
10+
fn test_create_access_token() {
11+
let token = create_access_token("client-123", Some("mcp read write"), 3600, TEST_SECRET);
12+
13+
// Token should have two parts separated by '.'
14+
let parts: Vec<&str> = token.split('.').collect();
15+
assert_eq!(parts.len(), 2, "Token should have payload.signature format");
16+
17+
// Both parts should be valid base64
18+
assert!(!parts[0].is_empty());
19+
assert!(!parts[1].is_empty());
20+
}
21+
22+
#[test]
23+
fn test_create_refresh_token() {
24+
let token = create_refresh_token("client-123", Some("mcp"), TEST_SECRET);
25+
26+
let parts: Vec<&str> = token.split('.').collect();
27+
assert_eq!(parts.len(), 2);
28+
}
29+
30+
#[test]
31+
fn test_validate_token_success() {
32+
let token = create_access_token("test-client", Some("read"), 3600, TEST_SECRET);
33+
34+
let claims = validate_token(&token, TEST_SECRET);
35+
assert!(claims.is_some(), "Valid token should return claims");
36+
37+
let claims = claims.unwrap();
38+
assert_eq!(claims.client_id, "test-client");
39+
assert_eq!(claims.scope, Some("read".to_string()));
40+
}
41+
42+
#[test]
43+
fn test_validate_token_wrong_secret() {
44+
let token = create_access_token("client", None, 3600, TEST_SECRET);
45+
let wrong_secret = b"different_secret_key_32_bytes!!!";
46+
47+
let claims = validate_token(&token, wrong_secret);
48+
assert!(claims.is_none(), "Token signed with different secret should fail");
49+
}
50+
51+
#[test]
52+
fn test_validate_expired_token() {
53+
// Create token that expired 1 hour ago
54+
let token = create_access_token("client", None, -3600, TEST_SECRET);
55+
56+
let claims = validate_token(&token, TEST_SECRET);
57+
assert!(claims.is_none(), "Expired token should fail validation");
58+
}
59+
60+
#[test]
61+
fn test_validate_malformed_token() {
62+
let claims = validate_token("not.a.valid.token", TEST_SECRET);
63+
assert!(claims.is_none());
64+
65+
let claims = validate_token("", TEST_SECRET);
66+
assert!(claims.is_none());
67+
68+
let claims = validate_token("single_part_token", TEST_SECRET);
69+
assert!(claims.is_none());
70+
}
71+
72+
#[test]
73+
fn test_token_contains_timestamps() {
74+
let token = create_access_token("client", None, 3600, TEST_SECRET);
75+
76+
let claims = validate_token(&token, TEST_SECRET).unwrap();
77+
78+
// iat should be recent (within last minute)
79+
let now = chrono::Utc::now().timestamp();
80+
assert!(claims.iat >= now - 60);
81+
assert!(claims.iat <= now);
82+
83+
// exp should be in the future
84+
assert!(claims.exp > now);
85+
}
86+
87+
#[test]
88+
fn test_token_scope_optional() {
89+
// Token without scope
90+
let token = create_access_token("client", None, 3600, TEST_SECRET);
91+
let claims = validate_token(&token, TEST_SECRET).unwrap();
92+
93+
// Scope can be None when passed as null
94+
// The implementation may serialize None as null in JSON
95+
// Just verify token validates
96+
assert_eq!(claims.client_id, "client");
97+
}
98+
99+
#[test]
100+
fn test_different_clients_different_tokens() {
101+
let token1 = create_access_token("client-a", None, 3600, TEST_SECRET);
102+
let token2 = create_access_token("client-b", None, 3600, TEST_SECRET);
103+
104+
assert_ne!(token1, token2);
105+
106+
let claims1 = validate_token(&token1, TEST_SECRET).unwrap();
107+
let claims2 = validate_token(&token2, TEST_SECRET).unwrap();
108+
109+
assert_eq!(claims1.client_id, "client-a");
110+
assert_eq!(claims2.client_id, "client-b");
111+
}

tests/rust/tests/security/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
//! Tests for crypto, keychain, and JWT handling.
44
55
mod crypto;
6+
mod jwt;

0 commit comments

Comments
 (0)