use anyhow::{Context, Result};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Clone, Debug)]
pub struct ManifestFixture {
pub content: String,
pub name: String,
}
impl ManifestFixture {
#[must_use]
pub fn basic() -> Self {
Self {
name: "basic".to_string(),
content: r#"
[sources]
official = "https://github.com/example-org/agpm-official.git"
community = "https://github.com/example-org/agpm-community.git"
[agents]
my-agent = { source = "official", path = "agents/my-agent.md", version = "v1.0.0" }
helper = { source = "community", path = "agents/helper.md", version = "^1.2.0" }
[snippets]
utils = { source = "official", path = "snippets/utils.md", version = "v1.0.0" }
"#
.trim()
.to_string(),
}
}
#[must_use]
pub fn with_local() -> Self {
Self {
name: "with_local".to_string(),
content: r#"
[sources]
official = "https://github.com/example-org/agpm-official.git"
[agents]
my-agent = { source = "official", path = "agents/my-agent.md", version = "v1.0.0" }
local-agent = { path = "../local-agents/helper.md" }
[snippets]
local-utils = { path = "./snippets/local-utils.md" }
"#
.trim()
.to_string(),
}
}
#[must_use]
pub fn invalid_syntax() -> Self {
Self {
name: "invalid_syntax".to_string(),
content: r#"
[sources
official = "https://github.com/example-org/agpm-official.git"
[agents]
my-agent = { source = "official", path = "agents/my-agent.md", version = "v1.0.0"
"#
.trim()
.to_string(),
}
}
#[must_use]
pub fn missing_fields() -> Self {
Self {
name: "missing_fields".to_string(),
content: r#"
[sources]
official = "https://github.com/example-org/agpm-official.git"
[agents]
incomplete-agent = { source = "official", path = "agents/test.md" } # Missing version
"#
.trim()
.to_string(),
}
}
#[must_use]
pub fn version_conflicts() -> Self {
Self {
name: "version_conflicts".to_string(),
content: r#"
[sources]
source1 = "https://github.com/example-org/agpm-repo1.git"
source2 = "https://github.com/example-org/agpm-repo2.git"
[agents]
agent1 = { source = "source1", path = "shared/lib.md", version = "v1.0.0" }
agent2 = { source = "source2", path = "shared/lib.md", version = "v2.0.0" }
"#
.trim()
.to_string(),
}
}
#[must_use]
pub fn empty() -> Self {
Self {
name: "empty".to_string(),
content: r"
# Empty agpm.toml file
# No sources or dependencies defined
"
.trim()
.to_string(),
}
}
pub fn write_to(&self, dir: &Path) -> Result<PathBuf> {
let manifest_path = dir.join("agpm.toml");
fs::write(&manifest_path, &self.content)?;
Ok(manifest_path)
}
}
#[derive(Clone, Debug)]
pub struct LockfileFixture {
pub content: String,
pub name: String,
}
impl LockfileFixture {
#[must_use]
pub fn basic() -> Self {
Self {
name: "basic".to_string(),
content: r#"
# Auto-generated lockfile - DO NOT EDIT
version = 1
[[sources]]
name = "official"
url = "https://github.com/example-org/agpm-official.git"
commit = "abc123456789abcdef123456789abcdef12345678"
fetched_at = "2024-01-01T00:00:00Z"
[[sources]]
name = "community"
url = "https://github.com/example-org/agpm-community.git"
commit = "def456789abcdef123456789abcdef123456789ab"
fetched_at = "2024-01-01T00:00:00Z"
[[agents]]
name = "my-agent"
source = "official"
path = "agents/my-agent.md"
version = "v1.0.0"
resolved_commit = "abc123456789abcdef123456789abcdef12345678"
checksum = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
installed_at = "agents/my-agent.md"
"#
.trim()
.to_string(),
}
}
pub fn write_to(&self, dir: &Path) -> Result<PathBuf> {
let lockfile_path = dir.join("agpm.lock");
fs::write(&lockfile_path, &self.content)?;
Ok(lockfile_path)
}
}
#[derive(Clone, Debug)]
pub struct MarkdownFixture {
pub path: String,
pub content: String,
pub frontmatter: Option<HashMap<String, String>>,
}
impl MarkdownFixture {
#[must_use]
pub fn agent(name: &str) -> Self {
let mut frontmatter = HashMap::new();
frontmatter.insert("type".to_string(), "agent".to_string());
frontmatter.insert("name".to_string(), name.to_string());
frontmatter.insert("version".to_string(), "1.0.0".to_string());
frontmatter.insert("description".to_string(), format!("Test agent: {name}"));
Self {
path: format!("agents/{name}.md"),
content: format!(
r#"---
type: agent
name: {name}
version: 1.0.0
description: "Test agent: {name}"
---
# {name} Agent
This is a test agent that demonstrates the basic structure of a Claude Code agent.
## Usage
You can use this agent by importing it into your Claude Code project.
## Examples
```javascript
// Example usage of {name} agent
const {name} = require('{name}');
```
"#
),
frontmatter: Some(frontmatter),
}
}
#[must_use]
pub fn snippet(name: &str) -> Self {
let mut frontmatter = HashMap::new();
frontmatter.insert("type".to_string(), "snippet".to_string());
frontmatter.insert("name".to_string(), name.to_string());
frontmatter.insert("version".to_string(), "1.0.0".to_string());
Self {
path: format!("snippets/{name}.md"),
content: format!(
r#"---
type: snippet
name: {name}
version: 1.0.0
---
# {name} Snippet
This is a test snippet.
## Code
```javascript
// {name} snippet code
function {name}() {{
return "test";
}}
```
"#
),
frontmatter: Some(frontmatter),
}
}
#[must_use]
pub fn simple(name: &str, content: &str) -> Self {
Self {
path: format!("{name}.md"),
content: content.to_string(),
frontmatter: None,
}
}
pub fn write_to(&self, dir: &Path) -> Result<PathBuf> {
let file_path = dir.join(&self.path);
if let Some(parent) = file_path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&file_path, &self.content)?;
Ok(file_path)
}
}
#[derive(Clone, Debug)]
pub struct GitRepoFixture {
pub path: PathBuf,
pub files: Vec<MarkdownFixture>,
}
impl GitRepoFixture {
#[must_use]
pub const fn new(path: PathBuf) -> Self {
Self {
path,
files: Vec::new(),
}
}
#[must_use]
pub fn with_file(mut self, file: MarkdownFixture) -> Self {
self.files.push(file);
self
}
pub fn init(&self) -> Result<()> {
fs::create_dir_all(&self.path)?;
std::process::Command::new("git")
.args(["-C", self.path.to_str().unwrap(), "init"])
.output()
.context("Failed to initialize git repository")?;
std::process::Command::new("git")
.args(["-C", self.path.to_str().unwrap(), "config", "user.email", "test@example.com"])
.output()?;
std::process::Command::new("git")
.args(["-C", self.path.to_str().unwrap(), "config", "user.name", "Test User"])
.output()?;
for file in &self.files {
file.write_to(&self.path)?;
}
std::process::Command::new("git")
.args(["-C", self.path.to_str().unwrap(), "add", "."])
.output()?;
std::process::Command::new("git")
.args(["-C", self.path.to_str().unwrap(), "commit", "-m", "Initial commit"])
.output()?;
std::process::Command::new("git")
.args(["-C", self.path.to_str().unwrap(), "tag", "v1.0.0"])
.output()?;
Ok(())
}
}