use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use tempfile::TempDir;
pub fn vss_available() -> bool {
static VSS_AVAILABLE: OnceLock<bool> = OnceLock::new();
*VSS_AVAILABLE.get_or_init(|| match duckdb::Connection::open_in_memory() {
Ok(conn) => conn.execute_batch("INSTALL vss; LOAD vss;").is_ok(),
Err(_) => false,
})
}
#[macro_export]
macro_rules! require_vss {
() => {
if !$crate::common::vss_available() {
eprintln!("Skipping test: DuckDB VSS extension not available");
return;
}
};
}
pub struct TempProject {
dir: TempDir,
}
impl TempProject {
pub fn new() -> Self {
Self {
dir: TempDir::new().expect("Failed to create temp directory"),
}
}
pub fn with_sample_files() -> Self {
let project = Self::new();
project.create_sample_files();
project
}
pub fn path(&self) -> &Path {
self.dir.path()
}
#[allow(dead_code)]
pub fn path_buf(&self) -> PathBuf {
self.dir.path().to_path_buf()
}
pub fn create_file(&self, relative_path: &str, content: &str) {
let path = self.dir.path().join(relative_path);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).expect("Failed to create directories");
}
std::fs::write(&path, content).expect("Failed to write file");
}
pub fn create_dir(&self, relative_path: &str) {
let path = self.dir.path().join(relative_path);
std::fs::create_dir_all(&path).expect("Failed to create directory");
}
pub fn file_exists(&self, relative_path: &str) -> bool {
self.dir.path().join(relative_path).exists()
}
pub fn dir_exists(&self, relative_path: &str) -> bool {
self.dir.path().join(relative_path).is_dir()
}
pub fn read_file(&self, relative_path: &str) -> String {
let path = self.dir.path().join(relative_path);
std::fs::read_to_string(&path).expect("Failed to read file")
}
fn create_sample_files(&self) {
self.create_file(
"src/main.rs",
r#"fn main() {
println!("Hello, world!");
}
fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 2), 4);
}
}
"#,
);
self.create_file(
"src/utils.ts",
r#"export function formatDate(date: Date): string {
return date.toISOString();
}
export function parseJson<T>(json: string): T {
return JSON.parse(json);
}
export class Logger {
private prefix: string;
constructor(prefix: string) {
this.prefix = prefix;
}
log(message: string): void {
console.log(`[${this.prefix}] ${message}`);
}
}
"#,
);
self.create_file(
"scripts/helper.py",
r#"def calculate_sum(numbers: list[int]) -> int:
"""Calculate the sum of a list of numbers."""
return sum(numbers)
def process_data(data: dict) -> dict:
"""Process and transform input data."""
result = {}
for key, value in data.items():
result[key.upper()] = str(value)
return result
class DataProcessor:
def __init__(self, name: str):
self.name = name
def process(self, items: list) -> list:
return [item.strip() for item in items]
"#,
);
self.create_file(
"README.md",
r#"# Test Project
This is a test project for vyctor.
## Features
- Feature 1: Does something useful
- Feature 2: Does something else
## Installation
```bash
cargo install vyctor
```
## Usage
Run the main program:
```bash
cargo run
```
"#,
);
self.create_file(
"config.json",
r#"{
"name": "test-project",
"version": "1.0.0",
"settings": {
"debug": true,
"maxItems": 100
}
}
"#,
);
self.create_file("node_modules/some-package/index.js", "module.exports = {};");
}
pub fn init_vyctor(&self) {
self.create_dir(".vyctor");
self.create_file(
"vyctor.config.toml",
r#"[indexing]
include = ["**/*.rs", "**/*.ts", "**/*.py", "**/*.md", "**/*.json"]
exclude = ["**/node_modules/**", "**/target/**", "**/.git/**", "**/.vyctor/**"]
chunk_size = 1000
chunk_overlap = 200
[embedding]
provider = "local"
model = "sentence-transformers/all-MiniLM-L6-v2"
dimensions = 384
batch_size = 100
[embedding.openai]
api_key_env = "OPENAI_API_KEY"
base_url = "https://api.openai.com/v1"
[embedding.voyage]
api_key_env = "VOYAGE_API_KEY"
base_url = "https://api.voyageai.com/v1"
[embedding.local]
model = "sentence-transformers/all-MiniLM-L6-v2"
"#,
);
}
#[allow(dead_code)]
pub fn init_vyctor_mock(&self) {
self.create_dir(".vyctor");
self.create_file(
"vyctor.config.toml",
r#"[indexing]
include = ["**/*.rs", "**/*.ts", "**/*.py", "**/*.md", "**/*.json"]
exclude = ["**/node_modules/**", "**/target/**", "**/.git/**", "**/.vyctor/**"]
chunk_size = 500
chunk_overlap = 100
[embedding]
provider = "local"
model = "sentence-transformers/all-MiniLM-L6-v2"
dimensions = 384
batch_size = 10
[embedding.openai]
api_key_env = "OPENAI_API_KEY"
base_url = "https://api.openai.com/v1"
[embedding.voyage]
api_key_env = "VOYAGE_API_KEY"
base_url = "https://api.voyageai.com/v1"
[embedding.local]
model = "sentence-transformers/all-MiniLM-L6-v2"
"#,
);
}
}
impl Default for TempProject {
fn default() -> Self {
Self::new()
}
}
#[allow(dead_code)]
pub fn mock_config() -> vyctor::config::VyctorConfig {
vyctor::config::VyctorConfig {
indexing: vyctor::config::IndexingConfig {
include: vec![
"**/*.rs".to_string(),
"**/*.ts".to_string(),
"**/*.py".to_string(),
"**/*.md".to_string(),
],
exclude: vec![
"**/node_modules/**".to_string(),
"**/target/**".to_string(),
"**/.vyctor/**".to_string(),
],
chunk_size: 500,
chunk_overlap: 100,
semantic_chunking: true,
max_chunk_size: 3000,
},
embedding: vyctor::config::EmbeddingConfig {
provider: vyctor::config::EmbeddingProvider::Local,
dimensions: 384,
batch_size: 10,
..Default::default()
},
..Default::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_temp_project_creation() {
let project = TempProject::new();
assert!(project.path().exists());
}
#[test]
fn test_temp_project_with_files() {
let project = TempProject::with_sample_files();
assert!(project.file_exists("src/main.rs"));
assert!(project.file_exists("src/utils.ts"));
assert!(project.file_exists("scripts/helper.py"));
assert!(project.file_exists("README.md"));
}
#[test]
fn test_create_file() {
let project = TempProject::new();
project.create_file("test.txt", "hello world");
assert!(project.file_exists("test.txt"));
assert_eq!(project.read_file("test.txt"), "hello world");
}
#[test]
fn test_create_nested_file() {
let project = TempProject::new();
project.create_file("deep/nested/path/file.txt", "nested content");
assert!(project.file_exists("deep/nested/path/file.txt"));
}
#[test]
fn test_init_vyctor() {
let project = TempProject::new();
project.init_vyctor();
assert!(project.file_exists("vyctor.config.toml"));
assert!(project.dir_exists(".vyctor"));
}
#[test]
fn test_vss_check_doesnt_panic() {
let _ = vss_available();
}
}