Major additions: - Community Next.js app (port 18187) for browsing claims with API docs - stemedb-chaos crate: Fault injection, chaos testing, CRDT properties - Latent ingestion system: Reddit/FDA ingesters with ADK-Go agents - Disputed claims handling: Manual review workflows and validation - Aphoria security scanner: New extractors (SQL injection, command injection, weak crypto, TLS version), policy-based ignores, UAT reports - Docker infrastructure: Dockerfile, docker-compose.yml for full stack - VulnBank demo: Intentionally vulnerable multi-language test corpus SDK & API enhancements: - Source registry handlers for tracking data provenance - Metrics endpoint - Skeptic filtering improvements Code quality: - Split 14 large files (>500 lines) into focused modules - All files now under 500-line limit per project guidelines Documentation: - Chaos testing guide, circuit breakers, observability docs - Phase 7 UAT documentation updates - Martin Kleppmann technical writer agent Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
216 lines
5.6 KiB
Go
216 lines
5.6 KiB
Go
package adk
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/orchard9/stemedb-go/steme"
|
|
)
|
|
|
|
// mockClient implements EpistemeClient for testing
|
|
type mockClient struct {
|
|
queryFunc func(ctx context.Context, params steme.QueryParams) (*steme.QueryResult, error)
|
|
assertFunc func(ctx context.Context, assertion steme.Assertion) (string, error)
|
|
traceFunc func(ctx context.Context, params steme.TraceParams) (*steme.TraceResult, error)
|
|
supersedeFunc func(ctx context.Context, params steme.SupersedeParams) (*steme.SupersedeResult, error)
|
|
}
|
|
|
|
func (m *mockClient) Query(ctx context.Context, params steme.QueryParams) (*steme.QueryResult, error) {
|
|
if m.queryFunc != nil {
|
|
return m.queryFunc(ctx, params)
|
|
}
|
|
return &steme.QueryResult{}, nil
|
|
}
|
|
|
|
func (m *mockClient) Assert(ctx context.Context, assertion steme.Assertion) (string, error) {
|
|
if m.assertFunc != nil {
|
|
return m.assertFunc(ctx, assertion)
|
|
}
|
|
return "mock_hash", nil
|
|
}
|
|
|
|
func (m *mockClient) Trace(ctx context.Context, params steme.TraceParams) (*steme.TraceResult, error) {
|
|
if m.traceFunc != nil {
|
|
return m.traceFunc(ctx, params)
|
|
}
|
|
return &steme.TraceResult{}, nil
|
|
}
|
|
|
|
func (m *mockClient) Supersede(ctx context.Context, params steme.SupersedeParams) (*steme.SupersedeResult, error) {
|
|
if m.supersedeFunc != nil {
|
|
return m.supersedeFunc(ctx, params)
|
|
}
|
|
return &steme.SupersedeResult{
|
|
Status: "superseded",
|
|
TargetHash: params.TargetHash,
|
|
SupersessionType: params.SupersessionType,
|
|
Timestamp: 1704067200,
|
|
}, nil
|
|
}
|
|
|
|
func TestQueryTool(t *testing.T) {
|
|
// Mock client that returns a test assertion
|
|
client := &mockClient{
|
|
queryFunc: func(ctx context.Context, params steme.QueryParams) (*steme.QueryResult, error) {
|
|
lifecycle := steme.LifecycleApproved
|
|
return &steme.QueryResult{
|
|
Assertions: []steme.AssertionResponse{
|
|
{
|
|
Hash: "test_hash",
|
|
Subject: "Tesla_Inc",
|
|
Predicate: "has_revenue",
|
|
Object: steme.NewNumberValue(96.7),
|
|
Confidence: 0.95,
|
|
SourceHash: "source_hash",
|
|
Lifecycle: lifecycle,
|
|
},
|
|
},
|
|
TotalCount: 1,
|
|
HasMore: false,
|
|
}, nil
|
|
},
|
|
}
|
|
|
|
tool := NewQueryTool(client)
|
|
|
|
// Test basic query
|
|
input := QueryInput{
|
|
Subject: "Tesla_Inc",
|
|
Predicate: "has_revenue",
|
|
Lens: "consensus",
|
|
}
|
|
|
|
inputBytes, err := json.Marshal(input)
|
|
if err != nil {
|
|
t.Fatalf("failed to marshal input: %v", err)
|
|
}
|
|
|
|
outputBytes, err := tool.Execute(context.Background(), inputBytes)
|
|
if err != nil {
|
|
t.Fatalf("query failed: %v", err)
|
|
}
|
|
|
|
var output QueryOutput
|
|
if err := json.Unmarshal(outputBytes, &output); err != nil {
|
|
t.Fatalf("failed to unmarshal output: %v", err)
|
|
}
|
|
|
|
// Verify output
|
|
if output.Confidence != 0.95 {
|
|
t.Errorf("expected confidence 0.95, got %f", output.Confidence)
|
|
}
|
|
|
|
if output.Lifecycle != "Approved" {
|
|
t.Errorf("expected lifecycle Approved, got %s", output.Lifecycle)
|
|
}
|
|
|
|
if output.QueryID == "" {
|
|
t.Error("expected non-empty query ID")
|
|
}
|
|
|
|
if len(output.Sources) == 0 {
|
|
t.Error("expected at least one source")
|
|
}
|
|
}
|
|
|
|
func TestQueryToolConfidenceThreshold(t *testing.T) {
|
|
// Mock client that returns low confidence
|
|
client := &mockClient{
|
|
queryFunc: func(ctx context.Context, params steme.QueryParams) (*steme.QueryResult, error) {
|
|
lifecycle := steme.LifecycleApproved
|
|
return &steme.QueryResult{
|
|
Assertions: []steme.AssertionResponse{
|
|
{
|
|
Hash: "test_hash",
|
|
Subject: "test",
|
|
Predicate: "test",
|
|
Object: steme.NewTextValue("test"),
|
|
Confidence: 0.6, // Low confidence
|
|
SourceHash: "source_hash",
|
|
Lifecycle: lifecycle,
|
|
},
|
|
},
|
|
TotalCount: 1,
|
|
}, nil
|
|
},
|
|
}
|
|
|
|
tool := NewQueryTool(client)
|
|
|
|
input := QueryInput{
|
|
Subject: "test",
|
|
Predicate: "test",
|
|
MinConfidence: 0.8, // Require high confidence
|
|
}
|
|
|
|
inputBytes, _ := json.Marshal(input)
|
|
outputBytes, err := tool.Execute(context.Background(), inputBytes)
|
|
if err != nil {
|
|
t.Fatalf("query failed: %v", err)
|
|
}
|
|
|
|
var output QueryOutput
|
|
if err := json.Unmarshal(outputBytes, &output); err != nil {
|
|
t.Fatalf("failed to unmarshal output: %v", err)
|
|
}
|
|
|
|
// Should have error about low confidence
|
|
if output.Error == "" {
|
|
t.Error("expected error for low confidence")
|
|
}
|
|
}
|
|
|
|
func TestAssertTool(t *testing.T) {
|
|
// Track what assertion was created
|
|
var capturedAssertion steme.Assertion
|
|
|
|
client := &mockClient{
|
|
assertFunc: func(ctx context.Context, assertion steme.Assertion) (string, error) {
|
|
capturedAssertion = assertion
|
|
return "created_hash", nil
|
|
},
|
|
}
|
|
|
|
tool := NewAssertTool(client)
|
|
|
|
input := AssertInput{
|
|
Subject: "Tesla_Inc",
|
|
Predicate: "has_revenue",
|
|
Object: 96.7,
|
|
SourceHash: "0000000000000000000000000000000000000000000000000000000000000000",
|
|
Confidence: 0.95,
|
|
Lifecycle: "approved",
|
|
}
|
|
|
|
inputBytes, _ := json.Marshal(input)
|
|
outputBytes, err := tool.Execute(context.Background(), inputBytes)
|
|
if err != nil {
|
|
t.Fatalf("assert failed: %v", err)
|
|
}
|
|
|
|
var output AssertOutput
|
|
if err := json.Unmarshal(outputBytes, &output); err != nil {
|
|
t.Fatalf("failed to unmarshal output: %v", err)
|
|
}
|
|
|
|
// Verify output
|
|
if !output.Success {
|
|
t.Errorf("expected success, got error: %s", output.Error)
|
|
}
|
|
|
|
if output.Hash != "created_hash" {
|
|
t.Errorf("expected hash created_hash, got %s", output.Hash)
|
|
}
|
|
|
|
// Verify captured assertion
|
|
if capturedAssertion.Subject != "Tesla_Inc" {
|
|
t.Errorf("expected subject Tesla_Inc, got %s", capturedAssertion.Subject)
|
|
}
|
|
|
|
// Use approximate comparison for floating point
|
|
if capturedAssertion.Confidence < 0.94 || capturedAssertion.Confidence > 0.96 {
|
|
t.Errorf("expected confidence ~0.95, got %f", capturedAssertion.Confidence)
|
|
}
|
|
}
|