#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod red_phase_tests {
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
#[tokio::test]
#[cfg(feature = "go-ast")]
async fn red_test_go_file_gets_analyzed() {
let temp_dir = TempDir::new().unwrap();
let go_file = temp_dir.path().join("test.go");
fs::write(
&go_file,
r#"
package main
import "fmt"
type Message struct {
Type string
Value int
}
func ProcessMessage(msg Message) string {
if msg.Type == "ping" {
return fmt.Sprintf("pong: %d", msg.Value)
}
return "unknown"
}
func main() {
msg := Message{Type: "ping", Value: 42}
result := ProcessMessage(msg)
fmt.Println(result)
}
"#,
)
.unwrap();
use crate::services::context::analyze_project;
let context = analyze_project(temp_dir.path(), "stable").await.unwrap();
let go_file_found = context.files.iter().any(|f| f.path.contains("test.go"));
assert!(go_file_found, "Go file must be discovered and analyzed");
let go_context = context
.files
.iter()
.find(|f| f.path.contains("test.go"))
.expect("Must find Go file in context");
let functions: Vec<String> = go_context
.items
.iter()
.filter_map(|item| {
if let crate::services::context::AstItem::Function { name, .. } = item {
Some(name.clone())
} else {
None
}
})
.collect();
let has_process_message = functions.iter().any(|f| f.contains("ProcessMessage"));
let has_main = functions.iter().any(|f| f.contains("main"));
assert!(
has_process_message,
"Go function 'ProcessMessage' must be extracted, found functions: {:?}",
functions
);
assert!(
has_main,
"Go function 'main' must be extracted, found functions: {:?}",
functions
);
assert!(
!functions.is_empty(),
"Go file must have functions extracted"
);
}
#[tokio::test]
async fn red_test_typescript_file_analyzed_correctly() {
let temp_dir = TempDir::new().unwrap();
let ts_file = temp_dir.path().join("test.ts");
fs::write(
&ts_file,
r#"
export interface Message {
type: "ping" | "pong";
value: number;
}
class MessageProcessor {
private count: number = 0;
process(msg: Message): string {
this.count++;
if (msg.type === "ping") {
return `pong: ${msg.value}`;
}
return "unknown";
}
getCount(): number {
return this.count;
}
}
export function createProcessor(): MessageProcessor {
return new MessageProcessor();
}
"#,
)
.unwrap();
use crate::services::context::analyze_project;
let context = analyze_project(temp_dir.path(), "stable").await.unwrap();
let ts_file_found = context.files.iter().any(|f| f.path.contains("test.ts"));
assert!(
ts_file_found,
"TypeScript file must be discovered and analyzed"
);
let ts_context = context
.files
.iter()
.find(|f| f.path.contains("test.ts"))
.expect("Must find TypeScript file in context");
assert_ne!(
ts_context.language.to_lowercase(),
"rust",
"TypeScript file must NOT be detected as Rust, got: {}",
ts_context.language
);
let functions: Vec<String> = ts_context
.items
.iter()
.filter_map(|item| {
if let crate::services::context::AstItem::Function { name, .. } = item {
Some(name.clone())
} else {
None
}
})
.collect();
let has_process = functions
.iter()
.any(|f| f.contains("process") || f.contains("MessageProcessor"));
let has_create_processor = functions.iter().any(|f| f.contains("createProcessor"));
assert!(
has_process || has_create_processor || !functions.is_empty(),
"TypeScript methods/functions must be extracted, found functions: {:?}",
functions
);
}
#[tokio::test]
async fn red_test_javascript_file_analyzed() {
let temp_dir = TempDir::new().unwrap();
let js_file = temp_dir.path().join("test.js");
fs::write(
&js_file,
r#"
function calculateSum(a, b) {
if (a < 0 || b < 0) {
throw new Error("Negative numbers not allowed");
}
return a + b;
}
class Calculator {
constructor() {
this.history = [];
}
add(a, b) {
const result = calculateSum(a, b);
this.history.push({op: 'add', a, b, result});
return result;
}
}
module.exports = { Calculator, calculateSum };
"#,
)
.unwrap();
use crate::services::context::analyze_project;
let context = analyze_project(temp_dir.path(), "stable").await.unwrap();
let js_file_found = context.files.iter().any(|f| f.path.contains("test.js"));
assert!(js_file_found, "JavaScript file must be discovered");
let js_context = context
.files
.iter()
.find(|f| f.path.contains("test.js"))
.expect("Must find JavaScript file in context");
let function_count = js_context
.items
.iter()
.filter(|item| matches!(item, crate::services::context::AstItem::Function { .. }))
.count();
assert!(
function_count > 0 || js_context.language.to_lowercase().contains("javascript"),
"JavaScript file must be analyzed, found {} functions with language: {}",
function_count,
js_context.language
);
}
#[tokio::test]
#[cfg(feature = "go-ast")]
async fn red_test_multi_language_project_analyzes_all() {
let temp_dir = TempDir::new().unwrap();
fs::write(
temp_dir.path().join("lib.rs"),
r#"
/// Rust func.
pub fn rust_func(x: i32) -> i32 {
x * 2
}
"#,
)
.unwrap();
fs::write(
temp_dir.path().join("main.go"),
r#"
package main
func GoFunc(x int) int {
return x * 2
}
"#,
)
.unwrap();
fs::write(
temp_dir.path().join("app.ts"),
r#"
export function tsFunc(x: number): number {
return x * 2;
}
"#,
)
.unwrap();
use crate::services::context::analyze_project;
let context = analyze_project(temp_dir.path(), "stable").await.unwrap();
assert!(
context.files.iter().any(|f| f.path.contains("lib.rs")),
"Rust file must be discovered"
);
assert!(
context.files.iter().any(|f| f.path.contains("main.go")),
"Go file must be discovered"
);
assert!(
context.files.iter().any(|f| f.path.contains("app.ts")),
"TypeScript file must be discovered"
);
assert!(
context.files.len() >= 3,
"Should have at least 3 files, got {}",
context.files.len()
);
}
#[test]
fn red_test_language_detection_correctness() {
use crate::services::language_registry::LanguageRegistry;
let registry = LanguageRegistry::new();
let go_lang = registry.detect_language(&PathBuf::from("test.go"));
assert_eq!(
go_lang.name().to_lowercase(),
"go",
"Go files must be detected as 'go', got '{}'",
go_lang.name()
);
let ts_lang = registry.detect_language(&PathBuf::from("test.ts"));
assert_eq!(
ts_lang.name().to_lowercase(),
"typescript",
"TypeScript files must be detected as 'typescript', got '{}'",
ts_lang.name()
);
let js_lang = registry.detect_language(&PathBuf::from("test.js"));
assert_eq!(
js_lang.name().to_lowercase(),
"javascript",
"JavaScript files must be detected as 'javascript', got '{}'",
js_lang.name()
);
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod integration_tests {
use std::fs;
use tempfile::TempDir;
#[tokio::test]
#[cfg(feature = "go-ast")]
async fn test_go_analysis_end_to_end() {
let temp_dir = TempDir::new().unwrap();
let go_file = temp_dir.path().join("complex.go");
fs::write(
&go_file,
r#"
package main
func ComplexFunc(x, y, z int) int {
if x > 0 {
if y > 0 {
if z > 0 {
return x + y + z
}
return x + y
}
return x
}
return 0
}
"#,
)
.unwrap();
use crate::services::context::analyze_project;
let context = analyze_project(temp_dir.path(), "stable").await.unwrap();
let has_go_file = context.files.iter().any(|f| f.path.contains("complex.go"));
assert!(has_go_file, "Go file should be in context");
}
#[tokio::test]
async fn test_typescript_analysis_end_to_end() {
let temp_dir = TempDir::new().unwrap();
let ts_file = temp_dir.path().join("complex.ts");
fs::write(
&ts_file,
r#"
class Complex {
method(x: number, y: number): number {
if (x > 0) {
if (y > 0) {
return x + y;
}
return x;
}
return 0;
}
}
"#,
)
.unwrap();
use crate::services::context::analyze_project;
let context = analyze_project(temp_dir.path(), "stable").await.unwrap();
let has_ts_file = context.files.iter().any(|f| f.path.contains("complex.ts"));
assert!(has_ts_file, "TypeScript file should be in context");
}
}