mod common;
use axum::http::StatusCode;
use common::TestApp;
use skill_http::types::*;
#[tokio::test]
async fn test_list_skills_returns_200() {
let app = TestApp::new().await;
let req = TestApp::get_request("/api/skills?page=1&per_page=20");
let (status, body) = app.request(req).await;
assert_eq!(status, StatusCode::OK);
let response: PaginatedResponse<SkillSummary> = TestApp::parse_json(&body);
assert!(response.items.len() > 0);
assert_eq!(response.page, 1);
assert_eq!(response.per_page, 20);
}
#[tokio::test]
async fn test_list_skills_default_pagination() {
let app = TestApp::new().await;
let req = TestApp::get_request("/api/skills");
let (status, body) = app.request(req).await;
assert_eq!(status, StatusCode::OK);
let response: PaginatedResponse<SkillSummary> = TestApp::parse_json(&body);
assert_eq!(response.page, 1);
assert_eq!(response.per_page, 20); }
#[tokio::test]
async fn test_list_skills_pagination_boundaries() {
let app = TestApp::new().await;
let req = TestApp::get_request("/api/skills?page=1&per_page=2");
let (status, body) = app.request(req).await;
assert_eq!(status, StatusCode::OK);
let response: PaginatedResponse<SkillSummary> = TestApp::parse_json(&body);
assert_eq!(response.items.len(), 2);
assert!(response.total >= 2);
let req = TestApp::get_request("/api/skills?page=2&per_page=2");
let (status, body) = app.request(req).await;
assert_eq!(status, StatusCode::OK);
let response: PaginatedResponse<SkillSummary> = TestApp::parse_json(&body);
assert!(response.items.len() <= response.per_page);
}
#[tokio::test]
async fn test_list_skills_empty_page() {
let app = TestApp::new().await;
let req = TestApp::get_request("/api/skills?page=999&per_page=20");
let (status, body) = app.request(req).await;
assert_eq!(status, StatusCode::OK);
let response: PaginatedResponse<SkillSummary> = TestApp::parse_json(&body);
assert_eq!(response.items.len(), 0);
}
#[tokio::test]
async fn test_list_skills_contains_test_skill() {
let app = TestApp::new().await;
let req = TestApp::get_request("/api/skills");
let (status, body) = app.request(req).await;
assert_eq!(status, StatusCode::OK);
let response: PaginatedResponse<SkillSummary> = TestApp::parse_json(&body);
let test_skill = response
.items
.iter()
.find(|s| s.name == "test-skill")
.expect("test-skill should be in the list");
assert_eq!(test_skill.version, "0.1.0");
assert_eq!(test_skill.runtime, "wasm");
assert!(test_skill.description.contains("test"));
}
#[tokio::test]
async fn test_get_skill_returns_detail() {
let app = TestApp::new().await;
let req = TestApp::get_request("/api/skills/test-skill");
let (status, body) = app.request(req).await;
assert_eq!(status, StatusCode::OK);
let detail: SkillDetail = TestApp::parse_json(&body);
assert_eq!(detail.summary.name, "test-skill");
assert_eq!(detail.summary.version, "0.1.0");
assert!(detail.tools.is_empty() || !detail.tools.is_empty()); assert!(detail.instances.len() > 0);
}
#[tokio::test]
async fn test_get_skill_nonexistent_returns_404() {
let app = TestApp::new().await;
let req = TestApp::get_request("/api/skills/nonexistent-skill");
let (status, body) = app.request(req).await;
assert_eq!(status, StatusCode::NOT_FOUND);
let error: ApiError = TestApp::parse_json(&body);
assert!(error.message.to_lowercase().contains("not found")
|| error.message.to_lowercase().contains("skill"));
}
#[tokio::test]
async fn test_get_skill_detail_includes_instances() {
let app = TestApp::new().await;
let req = TestApp::get_request("/api/skills/test-skill");
let (status, body) = app.request(req).await;
assert_eq!(status, StatusCode::OK);
let detail: SkillDetail = TestApp::parse_json(&body);
assert!(detail.instances.len() > 0);
let default_instance = detail.instances.iter().find(|i| i.is_default);
assert!(default_instance.is_some(), "Should have a default instance");
}
#[tokio::test]
async fn test_get_skill_with_service_requirements() {
let app = TestApp::new().await;
let req = TestApp::get_request("/api/skills/kubernetes-skill");
let (status, body) = app.request(req).await;
assert_eq!(status, StatusCode::OK);
let detail: SkillDetail = TestApp::parse_json(&body);
assert_eq!(detail.summary.name, "kubernetes-skill");
assert!(detail.summary.required_services.is_empty() || !detail.summary.required_services.is_empty());
}
#[tokio::test]
async fn test_install_skill_success() {
let app = TestApp::new().await;
let body = r#"{
"source": "github:test/new-skill",
"name": "new-skill",
"version": null,
"instance_name": null,
"force": false
}"#;
let req = TestApp::post_request("/api/skills", body);
let (status, resp_body) = app.request(req).await;
assert!(
status == StatusCode::OK
|| status == StatusCode::BAD_REQUEST
|| status == StatusCode::INTERNAL_SERVER_ERROR
);
if status == StatusCode::OK {
let response: serde_json::Value = TestApp::parse_json(&resp_body);
assert!(response.is_object());
}
}
#[tokio::test]
async fn test_install_skill_invalid_json() {
let app = TestApp::new().await;
let body = r#"{ invalid json }"#;
let req = TestApp::post_request("/api/skills", body);
let (status, _) = app.request(req).await;
assert_eq!(status, StatusCode::BAD_REQUEST);
}
#[tokio::test]
async fn test_install_skill_missing_source() {
let app = TestApp::new().await;
let body = r#"{ "name": "new-skill" }"#;
let req = TestApp::post_request("/api/skills", body);
let (status, _) = app.request(req).await;
assert!(
status == StatusCode::BAD_REQUEST || status == StatusCode::UNPROCESSABLE_ENTITY
);
}
#[tokio::test]
async fn test_uninstall_skill_nonexistent_returns_404() {
let app = TestApp::new().await;
let req = TestApp::delete_request("/api/skills/nonexistent-skill");
let (status, _) = app.request(req).await;
assert_eq!(status, StatusCode::NOT_FOUND);
}
#[tokio::test]
async fn test_uninstall_existing_skill() {
let app = TestApp::new().await;
let req = TestApp::get_request("/api/skills/test-skill");
let (status, _) = app.request(req).await;
assert_eq!(status, StatusCode::OK);
let req = TestApp::delete_request("/api/skills/test-skill");
let (status, _) = app.request(req).await;
assert_eq!(status, StatusCode::NO_CONTENT);
}
#[tokio::test]
async fn test_api_error_response_structure() {
let app = TestApp::new().await;
let req = TestApp::get_request("/api/skills/nonexistent");
let (status, body) = app.request(req).await;
assert_eq!(status, StatusCode::NOT_FOUND);
let error: ApiError = TestApp::parse_json(&body);
assert!(!error.code.is_empty());
assert!(!error.message.is_empty());
}
#[tokio::test]
async fn test_invalid_pagination_parameters() {
let app = TestApp::new().await;
let req = TestApp::get_request("/api/skills?page=-1");
let (status, _) = app.request(req).await;
assert!(status == StatusCode::OK || status == StatusCode::BAD_REQUEST);
let req = TestApp::get_request("/api/skills?page=0");
let (status, _) = app.request(req).await;
assert!(status == StatusCode::OK || status == StatusCode::BAD_REQUEST);
let req = TestApp::get_request("/api/skills?per_page=10000");
let (status, _) = app.request(req).await;
assert_eq!(status, StatusCode::OK); }
#[tokio::test]
async fn test_malformed_query_parameters() {
let app = TestApp::new().await;
let req = TestApp::get_request("/api/skills?page=abc&per_page=xyz");
let (status, _) = app.request(req).await;
assert!(status == StatusCode::OK || status == StatusCode::BAD_REQUEST);
}
#[tokio::test]
async fn test_post_without_content_type() {
let app = TestApp::new().await;
let req = axum::http::Request::builder()
.method("POST")
.uri("/api/skills")
.body(axum::body::Body::from(r#"{"source": "test"}"#))
.unwrap();
let (status, _) = app.request(req).await;
assert!(
status == StatusCode::BAD_REQUEST
|| status == StatusCode::OK
|| status == StatusCode::UNSUPPORTED_MEDIA_TYPE
);
}
#[tokio::test]
async fn test_filter_skills_by_runtime() {
let app = TestApp::new().await;
let req = TestApp::get_request("/api/skills?runtime=wasm");
let (status, body) = app.request(req).await;
if status == StatusCode::OK {
let response: PaginatedResponse<SkillSummary> = TestApp::parse_json(&body);
for skill in &response.items {
assert_eq!(skill.runtime, "wasm");
}
}
}
#[tokio::test]
async fn test_filter_skills_by_source_prefix() {
let app = TestApp::new().await;
let req = TestApp::get_request("/api/skills?source=github:");
let (status, body) = app.request(req).await;
assert_eq!(status, StatusCode::OK);
let response: PaginatedResponse<SkillSummary> = TestApp::parse_json(&body);
assert!(response.total > 0);
}