Skip to content

Commit 437f5e3

Browse files
authored
Merge pull request #1260 from dcSpark/fix/remove-tool-offerings
Nullify invoice references before deleting a tool offering.
2 parents b97c32b + e15baae commit 437f5e3

File tree

1 file changed

+106
-5
lines changed

1 file changed

+106
-5
lines changed

shinkai-libs/shinkai-sqlite/src/tool_payment_req_manager.rs

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,16 @@ impl SqliteManager {
5858
}
5959

6060
pub fn remove_tool_offering(&self, tool_key: &str) -> Result<(), SqliteManagerError> {
61-
let conn = self.get_connection()?;
62-
let mut stmt = conn.prepare("DELETE FROM tool_micropayments_requirements WHERE tool_key = ?1")?;
63-
64-
stmt.execute(params![tool_key])?;
65-
61+
let mut conn = self.get_connection()?;
62+
let mut transaction = conn.transaction()?;
63+
64+
// First, nullify references in invoices to prevent constraint violations
65+
transaction.execute("UPDATE invoices SET shinkai_offering_key = NULL WHERE shinkai_offering_key = ?1", params![tool_key])?;
66+
67+
// Then delete the tool offering
68+
transaction.execute("DELETE FROM tool_micropayments_requirements WHERE tool_key = ?1", params![tool_key])?;
69+
70+
transaction.commit()?;
6671
Ok(())
6772
}
6873

@@ -320,4 +325,100 @@ mod tests {
320325
.iter()
321326
.any(|offering| offering.tool_key == tool_offering2.tool_key));
322327
}
328+
329+
#[tokio::test]
330+
async fn test_remove_tool_offering_with_invoices() {
331+
use chrono::Utc;
332+
use shinkai_message_primitives::schemas::invoices::{Invoice, InvoiceStatusEnum};
333+
use shinkai_message_primitives::schemas::shinkai_name::ShinkaiName;
334+
use shinkai_message_primitives::schemas::shinkai_tool_offering::UsageTypeInquiry;
335+
use shinkai_message_primitives::schemas::wallet_mixed::PublicAddress;
336+
use rusqlite::params;
337+
338+
let manager = setup_test_db();
339+
let tool_key = "test_tool_key_with_invoice";
340+
341+
// First, create a tool offering
342+
let tool_offering = ShinkaiToolOffering {
343+
tool_key: tool_key.to_string(),
344+
usage_type: UsageType::PerUse(ToolPrice::Payment(vec![PaymentRequirements {
345+
scheme: "exact".to_string(),
346+
description: "Payment for service".to_string(),
347+
network: Network::BaseSepolia,
348+
max_amount_required: "1000".to_string(),
349+
resource: "https://shinkai.com".to_string(),
350+
mime_type: "application/json".to_string(),
351+
pay_to: "0x036CbD53842c5426634e7929541eC2318f3dCF7e".to_string(),
352+
max_timeout_seconds: 300,
353+
asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e".to_string(),
354+
output_schema: Some(serde_json::json!({})),
355+
extra: Some(serde_json::json!({
356+
"decimals": 6,
357+
"asset_id": "USDC"
358+
})),
359+
}])),
360+
meta_description: Some("meta_description".to_string()),
361+
};
362+
363+
// Insert the tool offering
364+
let result = manager.set_tool_offering(tool_offering.clone());
365+
assert!(result.is_ok());
366+
367+
// Create an invoice that references this tool offering
368+
let invoice = Invoice {
369+
invoice_id: "test_invoice_id".to_string(),
370+
parent_message_id: Some("test_parent_id".to_string()),
371+
provider_name: ShinkaiName::new("@@provider.shinkai".to_string()).unwrap(),
372+
requester_name: ShinkaiName::new("@@requester.shinkai".to_string()).unwrap(),
373+
shinkai_offering: tool_offering.clone(),
374+
expiration_time: Utc::now() + chrono::Duration::hours(12),
375+
status: InvoiceStatusEnum::Pending,
376+
payment: None,
377+
address: PublicAddress {
378+
network_id: Network::BaseSepolia,
379+
address_id: "0x1234567890123456789012345678901234567890".to_string(),
380+
},
381+
usage_type_inquiry: UsageTypeInquiry::PerUse,
382+
request_date_time: Utc::now(),
383+
invoice_date_time: Utc::now(),
384+
tool_data: None,
385+
result_str: None,
386+
response_date_time: None,
387+
};
388+
389+
// Insert the invoice (this creates the foreign key reference)
390+
let invoice_result = manager.set_invoice(&invoice);
391+
assert!(invoice_result.is_ok());
392+
393+
// Now try to remove the tool offering - this should succeed with the new implementation
394+
let remove_result = manager.remove_tool_offering(tool_key);
395+
println!("Remove tool offering result: {:?}", remove_result);
396+
397+
// The removal should now succeed because we handle foreign key constraints properly
398+
assert!(remove_result.is_ok(), "Tool offering removal should succeed");
399+
400+
// Verify that the tool offering was actually removed
401+
let get_result = manager.get_tool_offering(tool_key);
402+
assert!(get_result.is_err(), "Tool offering should no longer exist");
403+
404+
// Verify that the invoice still exists but with shinkai_offering_key set to NULL
405+
// Since get_invoice might not handle NULL foreign keys properly, let's check at the database level
406+
let conn = manager.get_connection().unwrap();
407+
let mut stmt = conn.prepare("SELECT invoice_id, shinkai_offering_key FROM invoices WHERE invoice_id = ?1").unwrap();
408+
let result: Result<(String, Option<String>), _> = stmt.query_row(params!["test_invoice_id"], |row| {
409+
Ok((row.get(0)?, row.get(1)?))
410+
});
411+
412+
match result {
413+
Ok((invoice_id, offering_key)) => {
414+
println!("Invoice still exists: {}", invoice_id);
415+
assert_eq!(invoice_id, "test_invoice_id");
416+
assert!(offering_key.is_none(), "shinkai_offering_key should be NULL after tool offering removal");
417+
println!("Verified: shinkai_offering_key is NULL as expected");
418+
},
419+
Err(err) => {
420+
panic!("Invoice should still exist after tool offering removal: {:?}", err);
421+
}
422+
}
423+
}
323424
}

0 commit comments

Comments
 (0)