use vynfi::{
Client, CreateApiKeyRequest, CreateConfigRequest, CreateScenarioRequest, CreateSessionRequest,
EstimateCostRequest, GenerateRequest, ListConfigsParams, ListJobsParams,
ListNotificationsParams, MarkReadRequest, TableSpec, UpdateApiKeyRequest, UpdateConfigRequest,
ValidateConfigRequest, VynFiError,
};
fn client() -> Client {
let api_key = std::env::var("VYNFI_API_KEY").expect("VYNFI_API_KEY must be set");
let mut builder = Client::builder(api_key);
if let Ok(url) = std::env::var("VYNFI_BASE_URL") {
builder = builder.base_url(url);
}
builder.build().expect("failed to build client")
}
#[tokio::test]
#[ignore]
async fn catalog_list_sectors() {
let c = client();
let sectors = c.catalog().list_sectors().await.unwrap();
assert!(!sectors.is_empty(), "expected at least one sector");
for s in §ors {
assert!(!s.slug.is_empty());
assert!(s.table_count > 0, "sector {} has 0 tables", s.slug);
}
}
#[tokio::test]
#[ignore]
async fn catalog_get_sector_retail() {
let c = client();
let sector = c.catalog().get_sector("retail").await.unwrap();
assert_eq!(sector.slug, "retail");
assert!(!sector.tables.is_empty(), "retail should have tables");
let names: Vec<&str> = sector.tables.iter().map(|t| t.name.as_str()).collect();
assert!(
names.contains(&"Journal Entries"),
"retail should include Journal Entries, got: {names:?}"
);
}
#[tokio::test]
#[ignore]
async fn catalog_get_sector_not_found() {
let c = client();
let err = c
.catalog()
.get_sector("nonexistent-sector-slug")
.await
.unwrap_err();
assert!(
matches!(err, VynFiError::NotFound(_)),
"expected NotFound, got: {err:?}"
);
}
#[tokio::test]
#[ignore]
async fn catalog_list() {
let c = client();
let items = c.catalog().list(None, None).await.unwrap();
assert!(!items.is_empty(), "expected at least one catalog item");
}
#[tokio::test]
#[ignore]
async fn catalog_list_filtered() {
let c = client();
let items = c.catalog().list(Some("retail"), None).await.unwrap();
for item in &items {
assert_eq!(item.slug, "retail");
}
}
#[tokio::test]
#[ignore]
async fn usage_summary() {
let c = client();
let summary = c.usage().summary(None).await.unwrap();
assert!(summary.period_days > 0);
}
#[tokio::test]
#[ignore]
async fn usage_daily() {
let c = client();
let resp = c.usage().daily(Some(7)).await.unwrap();
assert!(resp.daily.len() <= 7);
}
#[tokio::test]
#[ignore]
async fn jobs_list() {
let c = client();
let list = c
.jobs()
.list(&ListJobsParams {
limit: Some(5),
..Default::default()
})
.await
.unwrap();
for job in &list.data {
assert!(!job.id.is_empty());
assert!(!job.status.is_empty());
}
}
#[tokio::test]
#[ignore]
async fn jobs_get_not_found() {
let c = client();
let err = c
.jobs()
.get("00000000-0000-0000-0000-000000000000")
.await
.unwrap_err();
assert!(
matches!(err, VynFiError::NotFound(_)),
"expected NotFound, got: {err:?}"
);
}
#[tokio::test]
#[ignore]
async fn jobs_generate_quick_and_download() {
let c = client();
let req = GenerateRequest::new(
vec![TableSpec {
name: "journal_entries".to_string(),
rows: 10,
base_rate: None,
}],
"retail",
);
let resp = c.jobs().generate_quick(&req).await.unwrap();
assert_eq!(resp.status, "completed");
assert!(resp.rows_generated > 0);
assert!(resp.credits_used > 0);
let bytes = c.jobs().download(&resp.id).await.unwrap();
assert!(!bytes.is_empty());
}
#[tokio::test]
#[ignore]
async fn jobs_generate_async() {
let c = client();
let req = GenerateRequest::new(
vec![TableSpec {
name: "journal_entries".to_string(),
rows: 10,
base_rate: None,
}],
"retail",
);
let resp = c.jobs().generate(&req).await.unwrap();
assert!(!resp.id.is_empty());
assert!(!resp.status.is_empty());
assert!(resp.credits_reserved > 0);
}
#[tokio::test]
#[ignore]
async fn jobs_generate_validation_error() {
let c = client();
let req = GenerateRequest {
tables: vec![],
format: None,
sector_slug: Some("retail".to_string()),
options: None,
};
let err = c.jobs().generate_quick(&req).await.unwrap_err();
assert!(
matches!(err, VynFiError::Validation(_)),
"expected Validation, got: {err:?}"
);
}
#[tokio::test]
#[ignore]
async fn api_keys_lifecycle() {
let c = client();
let created = c
.api_keys()
.create(&CreateApiKeyRequest {
name: "integration-test-key".to_string(),
environment: Some("test".to_string()),
})
.await
.unwrap();
assert!(!created.id.is_empty());
assert!(!created.key.is_empty(), "full secret should be returned");
assert!(
created.key.starts_with("vf_test_"),
"test env key should have vf_test_ prefix, got: {}",
created.key
);
assert_eq!(created.name, "integration-test-key");
assert_eq!(created.environment, "test");
let key_id = created.id.clone();
let keys = c.api_keys().list().await.unwrap();
assert!(
keys.iter().any(|k| k.id == key_id),
"newly created key should appear in list"
);
let fetched = c.api_keys().get(&key_id).await.unwrap();
assert_eq!(fetched.id, key_id);
assert_eq!(fetched.name, "integration-test-key");
let updated = c
.api_keys()
.update(
&key_id,
&UpdateApiKeyRequest {
name: Some("integration-test-key-updated".to_string()),
scopes: None,
},
)
.await
.unwrap();
assert_eq!(updated.name, "integration-test-key-updated");
let revoked = c.api_keys().revoke(&key_id).await.unwrap();
assert_eq!(revoked.id, key_id);
assert_eq!(revoked.status, "revoked");
}
#[tokio::test]
#[ignore]
async fn quality_scores() {
let c = client();
let scores = c.quality().scores().await.unwrap();
for s in &scores {
assert!(!s.id.is_empty());
assert!(s.overall_score >= 0.0);
}
}
#[tokio::test]
#[ignore]
async fn quality_timeline() {
let c = client();
let timeline = c.quality().timeline(Some(7)).await.unwrap();
for day in &timeline {
assert!(day.score >= 0.0);
}
}
#[tokio::test]
#[ignore]
async fn billing_subscription() {
let c = client();
let sub = c.billing().subscription().await.unwrap();
assert!(!sub.tier.is_empty());
assert!(!sub.status.is_empty());
}
#[tokio::test]
#[ignore]
async fn billing_invoices() {
let c = client();
let invoices = c.billing().invoices().await.unwrap();
for inv in &invoices {
assert!(!inv.id.is_empty());
}
}
#[tokio::test]
#[ignore]
async fn catalog_list_templates() {
let c = client();
let templates = c.catalog().list_templates(None).await.unwrap();
assert!(!templates.is_empty(), "expected at least one template");
for t in &templates {
assert!(!t.slug.is_empty());
assert!(!t.name.is_empty());
assert!(!t.sector.is_empty());
}
}
#[tokio::test]
#[ignore]
async fn catalog_list_templates_filtered() {
let c = client();
let templates = c.catalog().list_templates(Some("retail")).await.unwrap();
for t in &templates {
assert_eq!(t.sector, "retail", "expected only retail templates");
}
}
#[tokio::test]
#[ignore]
async fn configs_lifecycle() {
let c = client();
let created = c
.configs()
.create(&CreateConfigRequest {
name: "integration-test-config".to_string(),
description: Some("Created by integration test".to_string()),
config: serde_json::json!({"rows": 100, "sector": "retail"}),
source_template_id: None,
visibility: Some("private".to_string()),
tags: Some(vec!["test".to_string()]),
})
.await
.unwrap();
assert!(!created.id.is_empty());
assert_eq!(created.name, "integration-test-config");
assert_eq!(created.visibility, "private");
let config_id = created.id.clone();
let configs = c
.configs()
.list(&ListConfigsParams::default())
.await
.unwrap();
assert!(
configs.iter().any(|cfg| cfg.id == config_id),
"newly created config should appear in list"
);
let fetched = c.configs().get(&config_id).await.unwrap();
assert_eq!(fetched.id, config_id);
assert_eq!(fetched.name, "integration-test-config");
let updated = c
.configs()
.update(
&config_id,
&UpdateConfigRequest {
name: Some("integration-test-config-updated".to_string()),
description: None,
config: None,
visibility: None,
tags: None,
},
)
.await
.unwrap();
assert_eq!(updated.name, "integration-test-config-updated");
let deleted = c.configs().delete(&config_id).await.unwrap();
assert!(deleted.deleted);
}
#[tokio::test]
#[ignore]
async fn configs_validate() {
let c = client();
let resp = c
.configs()
.validate(&ValidateConfigRequest {
config: serde_json::json!({"rows": 1000, "sector": "retail"}),
partial: None,
step: None,
})
.await
.unwrap();
assert!(resp.valid || !resp.errors.is_empty());
}
#[tokio::test]
#[ignore]
async fn configs_estimate_cost() {
let c = client();
let resp = c
.configs()
.estimate_cost(&EstimateCostRequest {
config: serde_json::json!({"rows": 1000, "sector": "retail"}),
})
.await
.unwrap();
assert!(resp.base_credits > 0);
assert!(resp.total_credits > 0);
assert!(!resp.balance.status.is_empty());
}
#[tokio::test]
#[ignore]
async fn credits_balance() {
let c = client();
let resp = c.credits().balance().await.unwrap();
assert!(resp.total_prepaid_credits >= 0);
}
#[tokio::test]
#[ignore]
async fn credits_history() {
let c = client();
let resp = c.credits().history().await.unwrap();
for batch in &resp.batches {
assert!(!batch.id.is_empty());
assert!(!batch.pack.is_empty());
}
}
#[tokio::test]
#[ignore]
async fn sessions_list() {
let c = client();
let sessions = c.sessions().list().await.unwrap();
for s in &sessions {
assert!(!s.id.is_empty());
assert!(!s.status.is_empty());
}
}
#[tokio::test]
#[ignore]
async fn sessions_create() {
let c = client();
let session = c
.sessions()
.create(&CreateSessionRequest {
name: "integration-test-session".to_string(),
fiscal_year_start: "2026-01-01".to_string(),
period_length_months: 3,
periods: 4,
generation_config: serde_json::json!({"rows": 100, "sector": "retail"}),
})
.await
.unwrap();
assert!(!session.id.is_empty());
assert_eq!(session.name, "integration-test-session");
assert_eq!(session.periods_total, 4);
assert_eq!(session.period_length_months, 3);
assert_eq!(session.periods_generated, 0);
}
#[tokio::test]
#[ignore]
async fn scenarios_list() {
let c = client();
let scenarios = c.scenarios().list().await.unwrap();
for s in &scenarios {
assert!(!s.id.is_empty());
assert!(!s.status.is_empty());
}
}
#[tokio::test]
#[ignore]
async fn scenarios_create() {
let c = client();
let scenario = c
.scenarios()
.create(&CreateScenarioRequest {
name: "integration-test-scenario".to_string(),
template_id: "supply-chain".to_string(),
interventions: serde_json::json!({"fraudRate": 0.05}),
generation_config: serde_json::json!({"rows": 100, "sector": "retail"}),
})
.await
.unwrap();
assert!(!scenario.id.is_empty());
assert_eq!(scenario.name, "integration-test-scenario");
assert_eq!(scenario.status, "created");
}
#[tokio::test]
#[ignore]
async fn scenarios_templates() {
let c = client();
let templates = c.scenarios().templates().await.unwrap();
for t in &templates {
assert!(!t.id.is_empty());
assert!(!t.name.is_empty());
}
}
#[tokio::test]
#[ignore]
async fn notifications_list() {
let c = client();
let notifs = c
.notifications()
.list(&ListNotificationsParams::default())
.await
.unwrap();
for n in ¬ifs {
assert!(!n.id.is_empty());
assert!(!n.title.is_empty());
}
}
#[tokio::test]
#[ignore]
async fn notifications_mark_all_read() {
let c = client();
c.notifications()
.mark_read(&MarkReadRequest {
ids: None,
all: Some(true),
})
.await
.unwrap();
}