use fob_bundler::{BuildOptions, CacheConfig};
use tempfile::TempDir;
#[tokio::test]
async fn test_cache_hit_and_miss() {
let temp = TempDir::new().unwrap();
let cache_dir = temp.path().join("cache");
let result1 = BuildOptions::new("virtual:entry.js")
.virtual_file("virtual:entry.js", "export const x = 1;")
.cache_dir(&cache_dir)
.outfile(temp.path().join("out1.js"))
.build()
.await
.unwrap();
assert_eq!(result1.output.assets().count(), 1);
let result2 = BuildOptions::new("virtual:entry.js")
.virtual_file("virtual:entry.js", "export const x = 1;")
.cache_dir(&cache_dir)
.outfile(temp.path().join("out2.js"))
.build()
.await
.unwrap();
assert_eq!(result2.output.assets().count(), 1);
let chunks1: Vec<_> = result1.output.chunks().collect();
let code1 = &chunks1[0].code;
let chunks2: Vec<_> = result2.output.chunks().collect();
let code2 = &chunks2[0].code;
assert_eq!(code1, code2, "Cached result should match original build");
}
#[tokio::test]
async fn test_cache_key_determinism() {
let temp = TempDir::new().unwrap();
let cache_dir = temp.path().join("cache");
let result1 = BuildOptions::new("virtual:a.js")
.virtual_file("virtual:a.js", "export const a = 1;")
.virtual_file("virtual:b.js", "export const b = 2;")
.cache_dir(&cache_dir)
.outfile(temp.path().join("out1.js"))
.build()
.await
.unwrap();
let result2 = BuildOptions::new("virtual:a.js")
.virtual_file("virtual:b.js", "export const b = 2;")
.virtual_file("virtual:a.js", "export const a = 1;")
.cache_dir(&cache_dir)
.outfile(temp.path().join("out2.js"))
.build()
.await
.unwrap();
assert_eq!(
result1.output.assets().count(),
result2.output.assets().count()
);
}
#[tokio::test]
async fn test_cache_invalidation_on_content_change() {
let temp = TempDir::new().unwrap();
let cache_dir = temp.path().join("cache");
let result1 = BuildOptions::new("virtual:entry.js")
.virtual_file("virtual:entry.js", "export const x = 1;")
.cache_dir(&cache_dir)
.outfile(temp.path().join("out1.js"))
.build()
.await
.unwrap();
let chunks1: Vec<_> = result1.output.chunks().collect();
let code1 = &chunks1[0].code;
let result2 = BuildOptions::new("virtual:entry.js")
.virtual_file("virtual:entry.js", "export const x = 2;")
.cache_dir(&cache_dir)
.outfile(temp.path().join("out2.js"))
.build()
.await
.unwrap();
let chunks2: Vec<_> = result2.output.chunks().collect();
let code2 = &chunks2[0].code;
assert_ne!(code1, code2, "Changed content should invalidate cache");
}
#[tokio::test]
async fn test_force_rebuild() {
let temp = TempDir::new().unwrap();
let cache_dir = temp.path().join("cache");
let _result1 = BuildOptions::new("virtual:entry.js")
.virtual_file("virtual:entry.js", "export const x = 1;")
.cache_dir(&cache_dir)
.outfile(temp.path().join("out1.js"))
.build()
.await
.unwrap();
let result2 = BuildOptions::new("virtual:entry.js")
.virtual_file("virtual:entry.js", "export const x = 1;")
.cache_dir(&cache_dir)
.force_rebuild()
.outfile(temp.path().join("out2.js"))
.build()
.await
.unwrap();
assert_eq!(result2.output.assets().count(), 1);
}
#[tokio::test]
async fn test_cache_with_env_vars() {
let temp = TempDir::new().unwrap();
let cache_dir = temp.path().join("cache");
unsafe {
std::env::set_var("TEST_CACHE_VAR", "value1");
}
let cache_config = CacheConfig::new(&cache_dir).with_env_vars(["TEST_CACHE_VAR"]);
let result1 = BuildOptions::new("virtual:entry.js")
.virtual_file("virtual:entry.js", "export const x = 1;")
.cache(cache_config.clone())
.outfile(temp.path().join("out1.js"))
.build()
.await
.unwrap();
assert_eq!(result1.output.assets().count(), 1);
unsafe {
std::env::set_var("TEST_CACHE_VAR", "value2");
}
let cache_config2 = CacheConfig::new(&cache_dir).with_env_vars(["TEST_CACHE_VAR"]);
let result2 = BuildOptions::new("virtual:entry.js")
.virtual_file("virtual:entry.js", "export const x = 1;")
.cache(cache_config2)
.outfile(temp.path().join("out2.js"))
.build()
.await
.unwrap();
assert_eq!(result2.output.assets().count(), 1);
unsafe {
std::env::remove_var("TEST_CACHE_VAR");
}
}
#[tokio::test]
async fn test_cache_with_multiple_entries() {
let temp = TempDir::new().unwrap();
let cache_dir = temp.path().join("cache");
let result1 = BuildOptions::new_multiple(["virtual:a.js", "virtual:b.js"])
.virtual_file("virtual:a.js", "export const a = 1;")
.virtual_file("virtual:b.js", "export const b = 2;")
.cache_dir(&cache_dir)
.outdir(temp.path().join("dist1"))
.build()
.await
.unwrap();
let asset_count1 = result1.output.assets().count();
assert!(asset_count1 >= 2, "Should have at least 2 chunks");
let result2 = BuildOptions::new_multiple(["virtual:a.js", "virtual:b.js"])
.virtual_file("virtual:a.js", "export const a = 1;")
.virtual_file("virtual:b.js", "export const b = 2;")
.cache_dir(&cache_dir)
.outdir(temp.path().join("dist2"))
.build()
.await
.unwrap();
assert_eq!(result2.output.assets().count(), asset_count1);
}
#[tokio::test]
async fn test_cache_handles_corrupted_data() {
use std::fs;
use std::io::Write;
let temp = TempDir::new().unwrap();
let cache_dir = temp.path().join("cache");
let result1 = BuildOptions::new("virtual:entry.js")
.virtual_file("virtual:entry.js", "export const x = 42;")
.cache_dir(&cache_dir)
.outfile(temp.path().join("out1.js"))
.build()
.await
.unwrap();
assert_eq!(result1.output.assets().count(), 1);
let cache_db = cache_dir.join("cache.redb");
if cache_db.exists() {
let mut file = fs::OpenOptions::new().write(true).open(&cache_db).unwrap();
file.write_all(b"CORRUPTED DATA HERE").unwrap();
}
let result2 = BuildOptions::new("virtual:entry.js")
.virtual_file("virtual:entry.js", "export const x = 42;")
.cache_dir(&cache_dir)
.outfile(temp.path().join("out2.js"))
.build()
.await;
match result2 {
Ok(result) => {
assert_eq!(result.output.assets().count(), 1);
}
Err(e) => {
let err_msg = e.to_string();
assert!(
err_msg.contains("cache") || err_msg.contains("database") || err_msg.len() > 0,
"Error should be meaningful: {}",
err_msg
);
}
}
}