Files
didactyl/plans/tool_testing.md

6.8 KiB

Didactyl Tool Testing Plan

Overview

Testing agent-mediated tools is different from unit testing pure functions because the execution path spans multiple boundaries:

LLM schema interpretation → JSON argument generation → argument parsing → business logic → Nostr/network I/O → JSON response → LLM interpretation

This plan defines three testing layers, ordered by implementation priority.


Layer 1: Direct Tool Execution

Call tools_execute() directly with known JSON arguments and assert the JSON response structure.

Implementation

Add a --test-tool <name> <args_json> CLI flag to src/main.c that:

  1. Initializes config and nostr_handler (for network-dependent tools)
  2. Calls tools_execute(&ctx, name, args_json)
  3. Prints the raw JSON result to stdout
  4. Exits with 0 if success: true, 1 otherwise

Pure Computation Tools (no network needed)

Tool Test Command Expected
nostr_encode --test-tool nostr_encode '{"type":"npub","hex":"<64-char-hex>"}' success: true, uri starts with nostr:npub1
nostr_decode --test-tool nostr_decode '{"uri":"npub1..."}' success: true, pubkey is 64-char hex
nostr_encrypt --test-tool nostr_encrypt '{"recipient_pubkey":"<hex>","plaintext":"hello"}' success: true, ciphertext is base64
nostr_decrypt --test-tool nostr_decrypt '{"sender_pubkey":"<hex>","ciphertext":"<from encrypt>"}' success: true, plaintext is "hello"

Network-Dependent Tools (need relay or HTTP)

Tool Test Command Expected
nostr_nip05_lookup --test-tool nostr_nip05_lookup '{"identifier":"_@laantungir.com"}' success: true, pubkey returned
nostr_relay_info --test-tool nostr_relay_info '{"relay_url":"wss://relay.damus.io"}' success: true, info.basic.name present
nostr_relay_status --test-tool nostr_relay_status '{}' success: true, relay_count > 0
nostr_dm_send --test-tool nostr_dm_send '{"recipient_pubkey":"<hex>","message":"test"}' success: true
nostr_post --test-tool nostr_post '{"kind":1,"content":"test"}' success: true, event_id present
nostr_delete --test-tool nostr_delete '{"event_ids":["<64-char-hex>"]}' success: true
nostr_react --test-tool nostr_react '{"event_id":"<hex>","event_pubkey":"<hex>"}' success: true
nostr_profile_get --test-tool nostr_profile_get '{"pubkey":"<hex>"}' success: true, found: true/false
nostr_dm_send_nip17 --test-tool nostr_dm_send_nip17 '{"recipient_pubkey":"<hex>","message":"test"}' success: true
nostr_list_manage --test-tool nostr_list_manage '{"list_kind":10000,"action":"add","items":[["p","<hex>"]]}' success: true

Error Case Tests

Each tool should also be tested with:

  • Empty args: '{}' — should return descriptive error
  • Missing required fields — should return specific error message
  • Invalid hex strings — should reject gracefully
  • Double-encoded JSON string args — should parse correctly (regression for the invalid arguments JSON bug)

Layer 2: Schema-Parse Fidelity

Validates that the OpenAI function schema matches what executors actually accept.

Implementation

A Python script (tests/validate_schemas.py) that:

  1. Runs didactyl_static --dump-schemas (new flag) to get the tool schema JSON
  2. For each tool in the schema:
    • Generates a minimal valid argument payload from required + properties
    • Runs didactyl_static --test-tool <name> '<generated_json>'
    • Asserts exit code 0 or expected network error
  3. Reports mismatches between schema and executor expectations

What This Catches

  • Schema says required: ["hex"] but executor checks for "pubkey" — mismatch
  • Schema says type: "integer" but executor reads it as string
  • Schema advertises parameters the executor ignores
  • Missing required parameters in schema that executor demands

Layer 3: Agent Integration Testing (Live DM)

The most natural test — DM the running agent and verify it uses tools correctly.

Prerequisites

  • Local relay running at ws://127.0.0.1:7777
  • Didactyl running with valid config
  • A separate Nostr client (or script) to send/receive DMs

Test Prompts

# Tool DM Prompt Assert in Response
1 nostr_encode "Encode this pubkey as npub: <64-char hex>" Contains nostr:npub1
2 nostr_decode "Decode this npub: npub1..." Contains the hex pubkey
3 nostr_dm_send "Send a DM to <your-own-pubkey> saying 'hello test'" Confirms sent; you receive it
4 nostr_encrypt "Encrypt 'secret message' for <pubkey>" Contains base64 ciphertext
5 nostr_decrypt "Decrypt this NIP-44 payload: <ciphertext from #4>" Contains 'secret message'
6 nostr_nip05_lookup "Look up _@laantungir.com" Returns a pubkey
7 nostr_relay_info "Get NIP-11 info for wss://relay.damus.io" Returns relay name and supported NIPs
8 nostr_relay_status "Show me relay connection status" Lists connected relays with stats
9 nostr_react "React with 🤙 to event <id> from <pubkey>" Confirms kind 7 published
10 nostr_delete "Delete event <id>" Confirms kind 5 published
11 nostr_profile_get "Look up the profile for <pubkey>" Returns name/about/picture
12 nostr_post "Post a kind 1 note saying 'tool test'" Confirms published with event_id
13 nostr_list_manage "Add <pubkey> to my mute list" Confirms kind 10000 published
14 nostr_dm_send_nip17 "Send a private NIP-17 DM to <pubkey> saying 'gift wrap test'" Confirms gift wrap sent
15 nostr_post_readme "Publish the README to Nostr" Confirms kind 30023 with d=readme.md

Verification Methods

  • stdout logs: Watch [didactyl] executing tool call: <name> in terminal
  • context.log: Full LLM conversation including tool calls and results
  • Relay inspection: Query the local relay for published events
  • DM receipt: For DM tools, verify the message arrives at the recipient

Implementation Priority

  1. Immediate (no code changes): Run Layer 3 tests by DMing the live agent
  2. Next sprint: Add --test-tool CLI flag for Layer 1
  3. Later: Add --dump-schemas flag and Python schema validator for Layer 2
  4. CI integration: Wrap Layer 1 pure-computation tests in a shell script that runs after build_static.sh

Local Relay Setup for Testing

A local relay like strfry or nostr-rs-relay at ws://127.0.0.1:7777 provides:

  • All published events are observable and queryable
  • No rate limits or content policies
  • NIP-42 auth testing in isolation
  • Gift-wrapped NIP-17 DMs stay in your test environment
  • Database can be wiped between test runs