mirror of
https://github.com/Dicklesworthstone/coding_agent_session_search.git
synced 2026-09-22 11:59:25 +08:00
803 lines
26 KiB
Rust
803 lines
26 KiB
Rust
//! End-to-end integration tests for deployment workflows.
|
|
//!
|
|
//! This module tests deploy_github and deploy_cloudflare using local
|
|
//! infrastructure (bare git repos) with detailed logging artifacts.
|
|
//!
|
|
//! # Running
|
|
//!
|
|
//! ```bash
|
|
//! # Run all deploy E2E tests
|
|
//! cargo test --test e2e_deploy
|
|
//!
|
|
//! # Run with detailed logging
|
|
//! RUST_LOG=debug cargo test --test e2e_deploy -- --nocapture
|
|
//! ```
|
|
|
|
use anyhow::Result;
|
|
use coding_agent_search::pages::deploy_cloudflare::{
|
|
CloudflareDeployer, Prerequisites as CfPrereqs,
|
|
};
|
|
use coding_agent_search::pages::deploy_github::{GitHubDeployer, Prerequisites as GhPrereqs};
|
|
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::Command;
|
|
use tempfile::TempDir;
|
|
|
|
#[path = "util/mod.rs"]
|
|
mod util;
|
|
|
|
use util::e2e_log::PhaseTracker;
|
|
|
|
// =============================================================================
|
|
// Test Constants
|
|
// =============================================================================
|
|
|
|
const TEST_REPO_NAME: &str = "cass-archive-test";
|
|
|
|
// =============================================================================
|
|
// E2E Logger Support
|
|
// =============================================================================
|
|
|
|
fn tracker_for(test_name: &str) -> PhaseTracker {
|
|
PhaseTracker::new("e2e_deploy", test_name)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Local Git Infrastructure Helpers
|
|
// =============================================================================
|
|
|
|
/// Create a local bare git repository for testing
|
|
fn create_local_bare_repo(temp_dir: &Path, name: &str) -> Result<PathBuf> {
|
|
let repo_path = temp_dir.join(format!("{}.git", name));
|
|
let output = Command::new("git")
|
|
.args(["init", "--bare", repo_path.to_str().unwrap()])
|
|
.output()?;
|
|
|
|
if !output.status.success() {
|
|
anyhow::bail!(
|
|
"Failed to create bare repo: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
Ok(repo_path)
|
|
}
|
|
|
|
/// Create a minimal test bundle for deployment
|
|
fn create_test_bundle(temp_dir: &Path) -> Result<PathBuf> {
|
|
let bundle_dir = temp_dir.join("bundle");
|
|
let site_dir = bundle_dir.join("site");
|
|
fs::create_dir_all(&site_dir)?;
|
|
|
|
// Create minimal HTML
|
|
fs::write(
|
|
site_dir.join("index.html"),
|
|
r#"<!DOCTYPE html>
|
|
<html>
|
|
<head><title>CASS Archive</title></head>
|
|
<body>
|
|
<h1>CASS Archive Test</h1>
|
|
<p>This is a test deployment.</p>
|
|
</body>
|
|
</html>
|
|
"#,
|
|
)?;
|
|
|
|
// Create config
|
|
fs::write(
|
|
site_dir.join("config.json"),
|
|
r#"{"version":"1.0","encrypted":false}"#,
|
|
)?;
|
|
|
|
// Create assets directory
|
|
let assets_dir = site_dir.join("assets");
|
|
fs::create_dir_all(&assets_dir)?;
|
|
fs::write(
|
|
assets_dir.join("style.css"),
|
|
"body { font-family: sans-serif; }",
|
|
)?;
|
|
|
|
Ok(site_dir)
|
|
}
|
|
|
|
/// Clone a local bare repo to a working directory
|
|
fn clone_local_repo(bare_repo: &Path, work_dir: &Path) -> Result<()> {
|
|
let output = Command::new("git")
|
|
.args([
|
|
"clone",
|
|
bare_repo.to_str().unwrap(),
|
|
work_dir.to_str().unwrap(),
|
|
])
|
|
.output()?;
|
|
|
|
if !output.status.success() {
|
|
anyhow::bail!(
|
|
"Failed to clone: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn checkout_remote_branch(work_dir: &Path, branch_name: &str) -> Result<()> {
|
|
let remote_ref = format!("origin/{branch_name}");
|
|
let output = Command::new("git")
|
|
.current_dir(work_dir)
|
|
.args(["checkout", "--detach", remote_ref.as_str()])
|
|
.output()?;
|
|
|
|
if !output.status.success() {
|
|
anyhow::bail!(
|
|
"Failed to checkout {remote_ref}: stdout={} stderr={}",
|
|
String::from_utf8_lossy(&output.stdout),
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn read_git_blob(repo: &Path, branch_name: &str, blob_path: &str) -> Result<String> {
|
|
let spec = format!("{branch_name}:{blob_path}");
|
|
let output = Command::new("git")
|
|
.args([
|
|
"--git-dir",
|
|
repo.to_str().expect("utf8 repo path"),
|
|
"show",
|
|
&spec,
|
|
])
|
|
.output()?;
|
|
|
|
if !output.status.success() {
|
|
anyhow::bail!(
|
|
"Failed to read {spec}: stdout={} stderr={}",
|
|
String::from_utf8_lossy(&output.stdout),
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
|
|
Ok(String::from_utf8(output.stdout)?)
|
|
}
|
|
|
|
/// Create an orphan branch and push to local bare repo
|
|
fn create_and_push_orphan(work_dir: &Path, branch_name: &str, bundle_dir: &Path) -> Result<String> {
|
|
// Configure git
|
|
let _ = Command::new("git")
|
|
.current_dir(work_dir)
|
|
.args(["config", "user.email", "test@example.com"])
|
|
.output()?;
|
|
let _ = Command::new("git")
|
|
.current_dir(work_dir)
|
|
.args(["config", "user.name", "Test User"])
|
|
.output()?;
|
|
|
|
// Create orphan branch
|
|
let output = Command::new("git")
|
|
.current_dir(work_dir)
|
|
.args(["checkout", "--orphan", branch_name])
|
|
.output()?;
|
|
|
|
if !output.status.success() {
|
|
anyhow::bail!(
|
|
"Failed to create orphan branch: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
|
|
// Copy bundle contents
|
|
for entry in fs::read_dir(bundle_dir)? {
|
|
let entry = entry?;
|
|
let dest = work_dir.join(entry.file_name());
|
|
if entry.path().is_dir() {
|
|
copy_dir_recursive(&entry.path(), &dest)?;
|
|
} else {
|
|
fs::copy(entry.path(), dest)?;
|
|
}
|
|
}
|
|
|
|
// Add all files
|
|
let output = Command::new("git")
|
|
.current_dir(work_dir)
|
|
.args(["add", "--force", "--all", "--", "."])
|
|
.output()?;
|
|
|
|
if !output.status.success() {
|
|
anyhow::bail!(
|
|
"Failed to stage files: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
|
|
// Commit
|
|
let output = Command::new("git")
|
|
.current_dir(work_dir)
|
|
.args(["commit", "-m", "Deploy CASS archive"])
|
|
.output()?;
|
|
|
|
if !output.status.success() {
|
|
anyhow::bail!(
|
|
"Failed to commit: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
|
|
// Push
|
|
let output = Command::new("git")
|
|
.current_dir(work_dir)
|
|
.args(["push", "-u", "origin", branch_name, "--force"])
|
|
.output()?;
|
|
|
|
if !output.status.success() {
|
|
anyhow::bail!(
|
|
"Failed to push: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
|
|
// Get commit SHA
|
|
let output = Command::new("git")
|
|
.current_dir(work_dir)
|
|
.args(["rev-parse", "HEAD"])
|
|
.output()?;
|
|
|
|
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
|
}
|
|
|
|
fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<()> {
|
|
fs::create_dir_all(dst)?;
|
|
for entry in fs::read_dir(src)? {
|
|
let entry = entry?;
|
|
let src_path = entry.path();
|
|
let dst_path = dst.join(entry.file_name());
|
|
if src_path.is_dir() {
|
|
copy_dir_recursive(&src_path, &dst_path)?;
|
|
} else {
|
|
fs::copy(&src_path, &dst_path)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
// =============================================================================
|
|
// GitHub Deployer Tests - Prerequisites and Size Checks
|
|
// =============================================================================
|
|
|
|
#[test]
|
|
fn e2e_github_prerequisites_validation() {
|
|
let tracker = tracker_for("e2e_github_prerequisites_validation");
|
|
|
|
// Test 1: All prerequisites met
|
|
let start = tracker.start("all_ready", Some("Test prerequisites all ready"));
|
|
let prereqs = GhPrereqs {
|
|
gh_version: Some("gh version 2.40.0".to_string()),
|
|
gh_authenticated: true,
|
|
gh_username: Some("testuser".to_string()),
|
|
git_version: Some("git version 2.43.0".to_string()),
|
|
disk_space_mb: 10000,
|
|
estimated_size_mb: 100,
|
|
};
|
|
assert!(prereqs.is_ready(), "all prerequisites should be met");
|
|
assert!(prereqs.missing().is_empty(), "no missing prerequisites");
|
|
tracker.end("all_ready", Some("Test prerequisites all ready"), start);
|
|
|
|
// Test 2: gh CLI missing
|
|
let start = tracker.start("gh_missing", Some("Test gh CLI not installed"));
|
|
let prereqs = GhPrereqs {
|
|
gh_version: None,
|
|
gh_authenticated: false,
|
|
gh_username: None,
|
|
git_version: Some("git version 2.43.0".to_string()),
|
|
disk_space_mb: 10000,
|
|
estimated_size_mb: 100,
|
|
};
|
|
assert!(!prereqs.is_ready(), "should not be ready without gh CLI");
|
|
let missing = prereqs.missing();
|
|
assert!(
|
|
missing.iter().any(|m| m.contains("gh CLI")),
|
|
"should mention gh CLI: {:?}",
|
|
missing
|
|
);
|
|
tracker.end("gh_missing", Some("Test gh CLI not installed"), start);
|
|
|
|
// Test 3: git missing
|
|
let start = tracker.start("git_missing", Some("Test git not installed"));
|
|
let prereqs = GhPrereqs {
|
|
gh_version: Some("gh version 2.40.0".to_string()),
|
|
gh_authenticated: true,
|
|
gh_username: Some("testuser".to_string()),
|
|
git_version: None,
|
|
disk_space_mb: 10000,
|
|
estimated_size_mb: 100,
|
|
};
|
|
assert!(!prereqs.is_ready(), "should not be ready without git");
|
|
let missing = prereqs.missing();
|
|
assert!(
|
|
missing.iter().any(|m| m.contains("git")),
|
|
"should mention git: {:?}",
|
|
missing
|
|
);
|
|
tracker.end("git_missing", Some("Test git not installed"), start);
|
|
|
|
// Test 4: Not authenticated
|
|
let start = tracker.start("not_auth", Some("Test not authenticated"));
|
|
let prereqs = GhPrereqs {
|
|
gh_version: Some("gh version 2.40.0".to_string()),
|
|
gh_authenticated: false,
|
|
gh_username: None,
|
|
git_version: Some("git version 2.43.0".to_string()),
|
|
disk_space_mb: 10000,
|
|
estimated_size_mb: 100,
|
|
};
|
|
assert!(!prereqs.is_ready(), "should not be ready without auth");
|
|
let missing = prereqs.missing();
|
|
assert!(
|
|
missing.iter().any(|m| m.contains("authenticated")),
|
|
"should mention authentication: {:?}",
|
|
missing
|
|
);
|
|
tracker.end("not_auth", Some("Test not authenticated"), start);
|
|
}
|
|
|
|
#[test]
|
|
fn e2e_github_size_check() -> Result<()> {
|
|
let tracker = tracker_for("e2e_github_size_check");
|
|
|
|
let temp_dir = TempDir::new()?;
|
|
|
|
// Create a test bundle
|
|
let start = tracker.start("create_bundle", Some("Create test bundle for size check"));
|
|
let bundle_dir = create_test_bundle(temp_dir.path())?;
|
|
tracker.end(
|
|
"create_bundle",
|
|
Some("Create test bundle for size check"),
|
|
start,
|
|
);
|
|
|
|
// Check size
|
|
let start = tracker.start("check_size", Some("Run size check on bundle"));
|
|
let deployer = GitHubDeployer::new(TEST_REPO_NAME);
|
|
let size_result = deployer.check_size(&bundle_dir)?;
|
|
|
|
assert!(size_result.total_bytes > 0, "total bytes should be > 0");
|
|
assert!(
|
|
!size_result.exceeds_limit,
|
|
"small test bundle should not exceed limit"
|
|
);
|
|
assert!(
|
|
!size_result.has_oversized_files,
|
|
"test bundle should not have oversized files"
|
|
);
|
|
tracker.end("check_size", Some("Run size check on bundle"), start);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// =============================================================================
|
|
// Local Git Deployment Workflow Tests
|
|
// =============================================================================
|
|
|
|
#[test]
|
|
fn e2e_local_git_orphan_branch_workflow() -> Result<()> {
|
|
let tracker = tracker_for("e2e_local_git_orphan_branch_workflow");
|
|
|
|
let temp_dir = TempDir::new()?;
|
|
|
|
// Step 1: Create local bare repo
|
|
let start = tracker.start("create_bare_repo", Some("Create local bare git repo"));
|
|
let bare_repo = create_local_bare_repo(temp_dir.path(), TEST_REPO_NAME)?;
|
|
assert!(bare_repo.exists(), "bare repo should exist");
|
|
tracker.end(
|
|
"create_bare_repo",
|
|
Some("Create local bare git repo"),
|
|
start,
|
|
);
|
|
|
|
// Step 2: Clone to work directory
|
|
let start = tracker.start("clone_repo", Some("Clone to working directory"));
|
|
let work_dir = temp_dir.path().join("work");
|
|
clone_local_repo(&bare_repo, &work_dir)?;
|
|
assert!(work_dir.join(".git").exists(), "work dir should have .git");
|
|
tracker.end("clone_repo", Some("Clone to working directory"), start);
|
|
|
|
// Step 3: Create test bundle
|
|
let start = tracker.start("create_bundle", Some("Create test bundle"));
|
|
let bundle_dir = create_test_bundle(temp_dir.path())?;
|
|
assert!(
|
|
bundle_dir.join("index.html").exists(),
|
|
"bundle should have index.html"
|
|
);
|
|
tracker.end("create_bundle", Some("Create test bundle"), start);
|
|
|
|
// Step 4: Create orphan branch and push
|
|
let start = tracker.start("create_orphan_push", Some("Create orphan branch and push"));
|
|
let commit_sha = create_and_push_orphan(&work_dir, "gh-pages", &bundle_dir)?;
|
|
assert!(!commit_sha.is_empty(), "commit SHA should not be empty");
|
|
tracker.end(
|
|
"create_orphan_push",
|
|
Some("Create orphan branch and push"),
|
|
start,
|
|
);
|
|
|
|
// Step 5: Verify push to bare repo
|
|
let start = tracker.start("verify_push", Some("Verify content in bare repo"));
|
|
let verify_dir = temp_dir.path().join("verify");
|
|
clone_local_repo(&bare_repo, &verify_dir)?;
|
|
|
|
checkout_remote_branch(&verify_dir, "gh-pages")?;
|
|
|
|
assert!(
|
|
verify_dir.join("index.html").exists(),
|
|
"pushed content should include index.html"
|
|
);
|
|
assert!(
|
|
verify_dir.join("config.json").exists(),
|
|
"pushed content should include config.json"
|
|
);
|
|
assert!(
|
|
verify_dir.join("assets").is_dir(),
|
|
"pushed content should include assets/"
|
|
);
|
|
tracker.end("verify_push", Some("Verify content in bare repo"), start);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn e2e_local_git_bundle_copy_integrity() -> Result<()> {
|
|
let tracker = tracker_for("e2e_local_git_bundle_copy_integrity");
|
|
|
|
let temp_dir = TempDir::new()?;
|
|
|
|
// Create bundle with specific content
|
|
let start = tracker.start("create_bundle", Some("Create bundle with known content"));
|
|
let bundle_dir = temp_dir.path().join("bundle");
|
|
fs::create_dir_all(&bundle_dir)?;
|
|
|
|
let test_content = "Test content with special chars: <>&\"'\nLine 2\nLine 3";
|
|
fs::write(bundle_dir.join("test.txt"), test_content)?;
|
|
fs::write(
|
|
bundle_dir.join("index.html"),
|
|
"<html><body>Test</body></html>",
|
|
)?;
|
|
|
|
let nested = bundle_dir.join("nested/deep");
|
|
fs::create_dir_all(&nested)?;
|
|
fs::write(nested.join("file.json"), r#"{"key": "value"}"#)?;
|
|
tracker.end(
|
|
"create_bundle",
|
|
Some("Create bundle with known content"),
|
|
start,
|
|
);
|
|
|
|
// Create local git infrastructure
|
|
let start = tracker.start("setup_git", Some("Setup local git repos"));
|
|
let bare_repo = create_local_bare_repo(temp_dir.path(), "integrity-test")?;
|
|
let work_dir = temp_dir.path().join("work");
|
|
clone_local_repo(&bare_repo, &work_dir)?;
|
|
tracker.end("setup_git", Some("Setup local git repos"), start);
|
|
|
|
// Push bundle
|
|
let start = tracker.start("push_bundle", Some("Push bundle to local repo"));
|
|
let _commit_sha = create_and_push_orphan(&work_dir, "gh-pages", &bundle_dir)?;
|
|
tracker.end("push_bundle", Some("Push bundle to local repo"), start);
|
|
|
|
// Verify content integrity
|
|
let start = tracker.start("verify_integrity", Some("Verify bundle content integrity"));
|
|
// Check content matches
|
|
let pushed_content = read_git_blob(&bare_repo, "gh-pages", "test.txt")?;
|
|
assert_eq!(
|
|
pushed_content, test_content,
|
|
"pushed content should match original"
|
|
);
|
|
|
|
let pushed_nested = read_git_blob(&bare_repo, "gh-pages", "nested/deep/file.json")?;
|
|
assert_eq!(
|
|
pushed_nested, r#"{"key": "value"}"#,
|
|
"nested content should match"
|
|
);
|
|
tracker.end(
|
|
"verify_integrity",
|
|
Some("Verify bundle content integrity"),
|
|
start,
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// =============================================================================
|
|
// Cloudflare Deployer Tests - Prerequisites and Headers
|
|
// =============================================================================
|
|
|
|
#[test]
|
|
fn e2e_cloudflare_prerequisites_validation() {
|
|
let tracker = tracker_for("e2e_cloudflare_prerequisites_validation");
|
|
|
|
// Test 1: All prerequisites met with interactive auth
|
|
let start = tracker.start("interactive_auth", Some("Test with interactive auth"));
|
|
let prereqs = CfPrereqs {
|
|
wrangler_version: Some("wrangler 3.0.0".to_string()),
|
|
wrangler_authenticated: true,
|
|
account_email: Some("test@example.com".to_string()),
|
|
api_credentials_present: false,
|
|
account_id: None,
|
|
disk_space_mb: 10000,
|
|
};
|
|
assert!(prereqs.is_ready(), "should be ready with interactive auth");
|
|
assert!(prereqs.missing().is_empty(), "no missing prerequisites");
|
|
tracker.end(
|
|
"interactive_auth",
|
|
Some("Test with interactive auth"),
|
|
start,
|
|
);
|
|
|
|
// Test 2: All prerequisites met with API credentials
|
|
let start = tracker.start("api_auth", Some("Test with API credentials"));
|
|
let prereqs = CfPrereqs {
|
|
wrangler_version: Some("wrangler 3.0.0".to_string()),
|
|
wrangler_authenticated: false,
|
|
account_email: None,
|
|
api_credentials_present: true,
|
|
account_id: Some("abc123".to_string()),
|
|
disk_space_mb: 10000,
|
|
};
|
|
assert!(prereqs.is_ready(), "should be ready with API credentials");
|
|
tracker.end("api_auth", Some("Test with API credentials"), start);
|
|
|
|
// Test 3: Wrangler not installed
|
|
let start = tracker.start("wrangler_missing", Some("Test wrangler not installed"));
|
|
let prereqs = CfPrereqs {
|
|
wrangler_version: None,
|
|
wrangler_authenticated: false,
|
|
account_email: None,
|
|
api_credentials_present: false,
|
|
account_id: None,
|
|
disk_space_mb: 10000,
|
|
};
|
|
assert!(!prereqs.is_ready(), "should not be ready without wrangler");
|
|
let missing = prereqs.missing();
|
|
assert!(
|
|
missing
|
|
.iter()
|
|
.any(|m| m.contains("wrangler") || m.contains("install")),
|
|
"should mention wrangler: {:?}",
|
|
missing
|
|
);
|
|
tracker.end(
|
|
"wrangler_missing",
|
|
Some("Test wrangler not installed"),
|
|
start,
|
|
);
|
|
|
|
// Test 4: Not authenticated (neither method)
|
|
let start = tracker.start("not_auth", Some("Test not authenticated"));
|
|
let prereqs = CfPrereqs {
|
|
wrangler_version: Some("wrangler 3.0.0".to_string()),
|
|
wrangler_authenticated: false,
|
|
account_email: None,
|
|
api_credentials_present: false,
|
|
account_id: None,
|
|
disk_space_mb: 10000,
|
|
};
|
|
assert!(!prereqs.is_ready(), "should not be ready without any auth");
|
|
tracker.end("not_auth", Some("Test not authenticated"), start);
|
|
}
|
|
|
|
#[test]
|
|
fn e2e_cloudflare_headers_generation() -> Result<()> {
|
|
let tracker = tracker_for("e2e_cloudflare_headers_generation");
|
|
|
|
let temp_dir = TempDir::new()?;
|
|
let bundle_dir = temp_dir.path().join("bundle");
|
|
fs::create_dir_all(&bundle_dir)?;
|
|
|
|
// Generate headers
|
|
let start = tracker.start(
|
|
"generate_headers",
|
|
Some("Generate Cloudflare _headers file"),
|
|
);
|
|
let deployer = CloudflareDeployer::default();
|
|
deployer.generate_headers_file(&bundle_dir)?;
|
|
|
|
let headers_path = bundle_dir.join("_headers");
|
|
assert!(headers_path.exists(), "_headers file should be created");
|
|
tracker.end(
|
|
"generate_headers",
|
|
Some("Generate Cloudflare _headers file"),
|
|
start,
|
|
);
|
|
|
|
// Verify headers content
|
|
let start = tracker.start("verify_headers", Some("Verify headers content"));
|
|
let content = fs::read_to_string(&headers_path)?;
|
|
|
|
// Check COOP/COEP headers (critical for SharedArrayBuffer)
|
|
assert!(
|
|
content.contains("Cross-Origin-Opener-Policy"),
|
|
"should include COOP header"
|
|
);
|
|
assert!(
|
|
content.contains("Cross-Origin-Embedder-Policy"),
|
|
"should include COEP header"
|
|
);
|
|
assert!(
|
|
content.contains("same-origin"),
|
|
"COOP should be same-origin"
|
|
);
|
|
assert!(
|
|
content.contains("require-corp"),
|
|
"COEP should be require-corp"
|
|
);
|
|
|
|
// Check security headers
|
|
assert!(
|
|
content.contains("X-Content-Type-Options"),
|
|
"should include content type options"
|
|
);
|
|
assert!(content.contains("nosniff"), "should have nosniff");
|
|
assert!(
|
|
content.contains("X-Frame-Options"),
|
|
"should include frame options"
|
|
);
|
|
|
|
// Check caching headers
|
|
assert!(
|
|
content.contains("Cache-Control"),
|
|
"should include cache control"
|
|
);
|
|
tracker.end("verify_headers", Some("Verify headers content"), start);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn e2e_cloudflare_redirects_generation() -> Result<()> {
|
|
let tracker = tracker_for("e2e_cloudflare_redirects_generation");
|
|
|
|
let temp_dir = TempDir::new()?;
|
|
let bundle_dir = temp_dir.path().join("bundle");
|
|
fs::create_dir_all(&bundle_dir)?;
|
|
|
|
// Generate redirects
|
|
let start = tracker.start(
|
|
"generate_redirects",
|
|
Some("Generate Cloudflare _redirects file"),
|
|
);
|
|
let deployer = CloudflareDeployer::default();
|
|
deployer.generate_redirects_file(&bundle_dir)?;
|
|
|
|
let redirects_path = bundle_dir.join("_redirects");
|
|
assert!(redirects_path.exists(), "_redirects file should be created");
|
|
tracker.end(
|
|
"generate_redirects",
|
|
Some("Generate Cloudflare _redirects file"),
|
|
start,
|
|
);
|
|
|
|
// Verify redirects content
|
|
let start = tracker.start("verify_redirects", Some("Verify redirects content"));
|
|
let content = fs::read_to_string(&redirects_path)?;
|
|
|
|
// Check SPA fallback
|
|
assert!(
|
|
content.contains("/* /index.html 200"),
|
|
"should include SPA fallback rule"
|
|
);
|
|
tracker.end("verify_redirects", Some("Verify redirects content"), start);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// =============================================================================
|
|
// Combined Deploy Pipeline Tests
|
|
// =============================================================================
|
|
|
|
#[test]
|
|
fn e2e_full_deploy_bundle_preparation() -> Result<()> {
|
|
let tracker = tracker_for("e2e_full_deploy_bundle_preparation");
|
|
|
|
let temp_dir = TempDir::new()?;
|
|
|
|
// Create test bundle
|
|
let start = tracker.start("create_bundle", Some("Create test bundle"));
|
|
let bundle_dir = create_test_bundle(temp_dir.path())?;
|
|
tracker.end("create_bundle", Some("Create test bundle"), start);
|
|
|
|
// Add Cloudflare files
|
|
let start = tracker.start("add_cf_files", Some("Add Cloudflare-specific files"));
|
|
let deployer = CloudflareDeployer::default();
|
|
deployer.generate_headers_file(&bundle_dir)?;
|
|
deployer.generate_redirects_file(&bundle_dir)?;
|
|
tracker.end("add_cf_files", Some("Add Cloudflare-specific files"), start);
|
|
|
|
// Verify complete bundle
|
|
let start = tracker.start("verify_bundle", Some("Verify complete bundle"));
|
|
assert!(
|
|
bundle_dir.join("index.html").exists(),
|
|
"bundle should have index.html"
|
|
);
|
|
assert!(
|
|
bundle_dir.join("config.json").exists(),
|
|
"bundle should have config.json"
|
|
);
|
|
assert!(
|
|
bundle_dir.join("_headers").exists(),
|
|
"bundle should have _headers"
|
|
);
|
|
assert!(
|
|
bundle_dir.join("_redirects").exists(),
|
|
"bundle should have _redirects"
|
|
);
|
|
assert!(
|
|
bundle_dir.join("assets").is_dir(),
|
|
"bundle should have assets/"
|
|
);
|
|
tracker.end("verify_bundle", Some("Verify complete bundle"), start);
|
|
|
|
// Size check
|
|
let start = tracker.start("size_check", Some("Run size check on complete bundle"));
|
|
let gh_deployer = GitHubDeployer::new(TEST_REPO_NAME);
|
|
let size_result = gh_deployer.check_size(&bundle_dir)?;
|
|
assert!(
|
|
!size_result.exceeds_limit,
|
|
"bundle should not exceed size limit"
|
|
);
|
|
tracker.end(
|
|
"size_check",
|
|
Some("Run size check on complete bundle"),
|
|
start,
|
|
);
|
|
|
|
// Deploy to local git
|
|
let start = tracker.start("local_deploy", Some("Deploy to local git"));
|
|
let bare_repo = create_local_bare_repo(temp_dir.path(), "full-deploy-test")?;
|
|
let work_dir = temp_dir.path().join("work");
|
|
clone_local_repo(&bare_repo, &work_dir)?;
|
|
let commit_sha = create_and_push_orphan(&work_dir, "gh-pages", &bundle_dir)?;
|
|
assert!(!commit_sha.is_empty(), "should have commit SHA");
|
|
tracker.end("local_deploy", Some("Deploy to local git"), start);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn e2e_deploy_error_paths() -> Result<()> {
|
|
let tracker = tracker_for("e2e_deploy_error_paths");
|
|
|
|
let _temp_dir = TempDir::new()?;
|
|
|
|
// Test 1: Size check on non-existent directory
|
|
let start = tracker.start(
|
|
"nonexistent_dir",
|
|
Some("Size check on non-existent directory"),
|
|
);
|
|
let deployer = GitHubDeployer::new(TEST_REPO_NAME);
|
|
let result = deployer.check_size(Path::new("/nonexistent/path/12345"));
|
|
assert!(result.is_err(), "should error on non-existent path");
|
|
tracker.end(
|
|
"nonexistent_dir",
|
|
Some("Size check on non-existent directory"),
|
|
start,
|
|
);
|
|
|
|
// Test 2: Headers generation on non-existent directory
|
|
let start = tracker.start("headers_error", Some("Headers generation error path"));
|
|
let cf_deployer = CloudflareDeployer::default();
|
|
let result = cf_deployer.generate_headers_file(Path::new("/nonexistent/path/12345"));
|
|
assert!(result.is_err(), "should error on non-existent path");
|
|
tracker.end(
|
|
"headers_error",
|
|
Some("Headers generation error path"),
|
|
start,
|
|
);
|
|
|
|
// Test 3: Redirects generation on non-existent directory
|
|
let start = tracker.start("redirects_error", Some("Redirects generation error path"));
|
|
let result = cf_deployer.generate_redirects_file(Path::new("/nonexistent/path/12345"));
|
|
assert!(result.is_err(), "should error on non-existent path");
|
|
tracker.end(
|
|
"redirects_error",
|
|
Some("Redirects generation error path"),
|
|
start,
|
|
);
|
|
|
|
Ok(())
|
|
}
|