add tests of the signal-gateway-app-code infrastructure
This commit is contained in:
@@ -18,3 +18,6 @@ serde_json = { workspace = true }
|
|||||||
tar = { workspace = true }
|
tar = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tokio = { workspace = true, features = ["rt-multi-thread"] }
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ pub struct AppCodeConfig {
|
|||||||
/// GitHub repository in "owner/repo" format.
|
/// GitHub repository in "owner/repo" format.
|
||||||
pub github: GitHubRepo,
|
pub github: GitHubRepo,
|
||||||
/// Path to file containing the GitHub personal access token.
|
/// Path to file containing the GitHub personal access token.
|
||||||
pub token_file: PathBuf,
|
/// Optional for public repositories (unauthenticated access has lower rate limits).
|
||||||
|
pub token_file: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A file stored in memory from the tarball.
|
/// A file stored in memory from the tarball.
|
||||||
@@ -92,7 +93,7 @@ pub type ShaCallback = Arc<
|
|||||||
/// Downloads and caches GitHub tarballs for browsing application source code.
|
/// Downloads and caches GitHub tarballs for browsing application source code.
|
||||||
pub struct AppCode {
|
pub struct AppCode {
|
||||||
config: AppCodeConfig,
|
config: AppCodeConfig,
|
||||||
token: String,
|
token: Option<String>,
|
||||||
get_sha: ShaCallback,
|
get_sha: ShaCallback,
|
||||||
client: reqwest::Client,
|
client: reqwest::Client,
|
||||||
cache: Mutex<Option<CachedTarball>>,
|
cache: Mutex<Option<CachedTarball>>,
|
||||||
@@ -104,9 +105,11 @@ impl AppCode {
|
|||||||
/// The `get_sha` callback is called to determine which git SHA to download.
|
/// The `get_sha` callback is called to determine which git SHA to download.
|
||||||
/// It should return `None` if the SHA is not yet known.
|
/// It should return `None` if the SHA is not yet known.
|
||||||
pub fn new(config: AppCodeConfig, get_sha: ShaCallback) -> Result<Self, std::io::Error> {
|
pub fn new(config: AppCodeConfig, get_sha: ShaCallback) -> Result<Self, std::io::Error> {
|
||||||
let token = std::fs::read_to_string(&config.token_file)?
|
let token = config
|
||||||
.trim()
|
.token_file
|
||||||
.to_string();
|
.as_ref()
|
||||||
|
.map(|path| std::fs::read_to_string(path).map(|s| s.trim().to_string()))
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
config,
|
config,
|
||||||
@@ -188,13 +191,18 @@ impl AppCode {
|
|||||||
self.config.github.owner, self.config.github.repo, sha
|
self.config.github.owner, self.config.github.repo, sha
|
||||||
);
|
);
|
||||||
|
|
||||||
let response = self
|
let mut request = self
|
||||||
.client
|
.client
|
||||||
.get(&url)
|
.get(&url)
|
||||||
.header("Authorization", format!("Bearer {}", self.token))
|
|
||||||
.header("Accept", "application/vnd.github+json")
|
.header("Accept", "application/vnd.github+json")
|
||||||
.header("User-Agent", "signal-gateway")
|
.header("User-Agent", "signal-gateway")
|
||||||
.header("X-GitHub-Api-Version", "2022-11-28")
|
.header("X-GitHub-Api-Version", "2022-11-28");
|
||||||
|
|
||||||
|
if let Some(token) = &self.token {
|
||||||
|
request = request.header("Authorization", format!("Bearer {}", token));
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = request
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("HTTP request failed: {e}"))?;
|
.map_err(|e| format!("HTTP request failed: {e}"))?;
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
//! Integration tests for signal-gateway-app-code.
|
||||||
|
//!
|
||||||
|
//! These tests exercise the GitHub tarball download and file browsing functionality
|
||||||
|
//! against a real public repository at a pinned commit.
|
||||||
|
|
||||||
|
use signal_gateway_app_code::{AppCode, AppCodeConfig, GitHubRepo, ShaCallback};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Test against cbeck88/ver-stub-rs at a known commit.
|
||||||
|
/// This is a public repo, so no token is needed.
|
||||||
|
const TEST_OWNER: &str = "cbeck88";
|
||||||
|
const TEST_REPO: &str = "ver-stub-rs";
|
||||||
|
const TEST_SHA: &str = "79b98e25f27ae4f5dd73a5a3d8f37dad655a57e8";
|
||||||
|
|
||||||
|
fn create_test_app_code() -> AppCode {
|
||||||
|
let config = AppCodeConfig {
|
||||||
|
name: "test-app".to_string(),
|
||||||
|
github: GitHubRepo {
|
||||||
|
owner: TEST_OWNER.to_string(),
|
||||||
|
repo: TEST_REPO.to_string(),
|
||||||
|
},
|
||||||
|
token_file: None, // Public repo, no auth needed
|
||||||
|
};
|
||||||
|
|
||||||
|
let sha = TEST_SHA.to_string();
|
||||||
|
let sha_callback: ShaCallback = Arc::new(move || {
|
||||||
|
let sha = sha.clone();
|
||||||
|
Box::pin(async move { Ok(sha) })
|
||||||
|
});
|
||||||
|
|
||||||
|
AppCode::new(config, sha_callback).expect("Failed to create AppCode")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_ls_root() {
|
||||||
|
let app = create_test_app_code();
|
||||||
|
|
||||||
|
let result = app.ls(None).await.expect("ls failed");
|
||||||
|
|
||||||
|
// Verify expected top-level entries exist
|
||||||
|
assert!(result.contains("Cargo.toml"), "Should contain Cargo.toml");
|
||||||
|
assert!(result.contains("README.md"), "Should contain README.md");
|
||||||
|
assert!(result.contains("ver-stub/"), "Should contain ver-stub/ directory");
|
||||||
|
assert!(result.contains("ver-stub-build/"), "Should contain ver-stub-build/ directory");
|
||||||
|
assert!(result.contains("tests.sh"), "Should contain tests.sh");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_ls_subdirectory() {
|
||||||
|
let app = create_test_app_code();
|
||||||
|
|
||||||
|
let result = app.ls(Some("ver-stub")).await.expect("ls failed");
|
||||||
|
|
||||||
|
// Should have src/ directory and Cargo.toml
|
||||||
|
assert!(result.contains("src/"), "Should contain src/ directory");
|
||||||
|
assert!(result.contains("Cargo.toml"), "Should contain Cargo.toml");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_find_rust_files() {
|
||||||
|
let app = create_test_app_code();
|
||||||
|
|
||||||
|
let result = app.find(Some("*.rs")).await.expect("find failed");
|
||||||
|
|
||||||
|
// Should find Rust source files
|
||||||
|
assert!(result.contains("ver-stub/src/lib.rs"), "Should find ver-stub/src/lib.rs");
|
||||||
|
assert!(result.contains("ver-stub-build/src/lib.rs"), "Should find ver-stub-build/src/lib.rs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_find_with_path_pattern() {
|
||||||
|
let app = create_test_app_code();
|
||||||
|
|
||||||
|
let result = app.find(Some("ver-stub-build/src/*.rs")).await.expect("find failed");
|
||||||
|
|
||||||
|
// Should find files in ver-stub-build/src/
|
||||||
|
assert!(result.contains("ver-stub-build/src/lib.rs"), "Should find lib.rs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_cargo_toml() {
|
||||||
|
let app = create_test_app_code();
|
||||||
|
|
||||||
|
let result = app.read("Cargo.toml", None, None).await.expect("read failed");
|
||||||
|
|
||||||
|
// Verify content matches what we know is in the file
|
||||||
|
assert!(result.contains("[workspace]"), "Should contain [workspace]");
|
||||||
|
assert!(result.contains("ver-stub"), "Should contain ver-stub");
|
||||||
|
assert!(result.contains("ver-stub-build"), "Should contain ver-stub-build");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_with_line_range() {
|
||||||
|
let app = create_test_app_code();
|
||||||
|
|
||||||
|
// Read just the first 5 lines
|
||||||
|
let result = app.read("Cargo.toml", Some(1), Some(5)).await.expect("read failed");
|
||||||
|
|
||||||
|
// Should only have 5 lines
|
||||||
|
let line_count = result.lines().count();
|
||||||
|
assert_eq!(line_count, 5, "Should have exactly 5 lines");
|
||||||
|
|
||||||
|
// First line should be [workspace]
|
||||||
|
assert!(result.contains("[workspace]"), "First lines should contain [workspace]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_nonexistent_file() {
|
||||||
|
let app = create_test_app_code();
|
||||||
|
|
||||||
|
let result = app.read("nonexistent-file.txt", None, None).await;
|
||||||
|
|
||||||
|
assert!(result.is_err(), "Should fail for nonexistent file");
|
||||||
|
assert!(
|
||||||
|
result.unwrap_err().contains("not found"),
|
||||||
|
"Error should mention file not found"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_search_simple() {
|
||||||
|
let app = create_test_app_code();
|
||||||
|
|
||||||
|
let result = app.search("workspace", 0, None).await.expect("search failed");
|
||||||
|
|
||||||
|
// Should find "workspace" in Cargo.toml
|
||||||
|
assert!(result.contains("Cargo.toml"), "Should find match in Cargo.toml");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_search_with_context() {
|
||||||
|
let app = create_test_app_code();
|
||||||
|
|
||||||
|
let result = app.search("resolver", 2, None).await.expect("search failed");
|
||||||
|
|
||||||
|
// Should have context lines around the match
|
||||||
|
assert!(result.contains("Cargo.toml"), "Should find match in Cargo.toml");
|
||||||
|
// With context, should see surrounding lines
|
||||||
|
assert!(result.contains("[workspace]"), "Should show context including [workspace]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_search_with_path_prefix() {
|
||||||
|
let app = create_test_app_code();
|
||||||
|
|
||||||
|
// Search only in ver-stub-build directory
|
||||||
|
let result = app
|
||||||
|
.search("pub", 0, Some("ver-stub-build/src"))
|
||||||
|
.await
|
||||||
|
.expect("search failed");
|
||||||
|
|
||||||
|
// Should only find matches in ver-stub-build/src
|
||||||
|
for line in result.lines() {
|
||||||
|
if line.contains(":") && !line.starts_with('[') {
|
||||||
|
// This is a match line (path:line: content)
|
||||||
|
assert!(
|
||||||
|
line.starts_with("ver-stub-build/src"),
|
||||||
|
"All matches should be in ver-stub-build/src, got: {}",
|
||||||
|
line
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_search_no_matches() {
|
||||||
|
let app = create_test_app_code();
|
||||||
|
|
||||||
|
let result = app
|
||||||
|
.search("xyzzy_unlikely_string_12345", 0, None)
|
||||||
|
.await
|
||||||
|
.expect("search failed");
|
||||||
|
|
||||||
|
assert!(result.contains("No matches"), "Should report no matches");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user