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) } } func TestConstraintCheckTool(t *testing.T) { // Mock client that returns constraint assertions client := &mockClient{ queryFunc: func(ctx context.Context, params steme.QueryParams) (*steme.QueryResult, error) { lifecycle := steme.LifecycleApproved return &steme.QueryResult{ Assertions: []steme.AssertionResponse{ { Hash: "constraint1", Subject: "auth_jwt", Predicate: "must_use", Object: steme.NewTextValue("Ed25519"), Confidence: 1.0, SourceHash: "source", Lifecycle: lifecycle, }, { Hash: "constraint2", Subject: "auth_jwt", Predicate: "forbidden", Object: steme.NewTextValue("MD5"), Confidence: 1.0, SourceHash: "source", Lifecycle: lifecycle, }, }, TotalCount: 2, }, nil }, } tool := NewConstraintCheckTool(client) input := ConstraintCheckInput{ Context: "auth_jwt", } inputBytes, _ := json.Marshal(input) outputBytes, err := tool.Execute(context.Background(), inputBytes) if err != nil { t.Fatalf("constraint check failed: %v", err) } var output ConstraintCheckOutput if err := json.Unmarshal(outputBytes, &output); err != nil { t.Fatalf("failed to unmarshal output: %v", err) } // Verify constraints were returned if len(output.Constraints) != 2 { t.Errorf("expected 2 constraints, got %d", len(output.Constraints)) } // Check for must-use constraint foundMustUse := false for _, c := range output.Constraints { if c.MustUse == "Ed25519" { foundMustUse = true } } if !foundMustUse { t.Error("expected to find Ed25519 must-use constraint") } // Check for forbidden constraint foundForbidden := false for _, c := range output.Constraints { if c.Forbidden == "MD5" { foundForbidden = true } } if !foundForbidden { t.Error("expected to find MD5 forbidden constraint") } } func TestTraceTool(t *testing.T) { // Mock client that returns audit records agentID := "deadbeef00000000000000000000000000000000000000000000000000000000" subject := "Tesla_Inc" predicate := "has_revenue" resultHash := "result_hash_123" lens := steme.LensConsensus client := &mockClient{ traceFunc: func(ctx context.Context, params steme.TraceParams) (*steme.TraceResult, error) { return &steme.TraceResult{ Audits: []steme.QueryAuditRecord{ { QueryID: "query_123", AgentID: &agentID, Timestamp: 1704067200, Params: steme.QueryParamsAudit{ Subject: &subject, Predicate: &predicate, Lens: &lens, }, ResultHash: &resultHash, ResultConfidence: 0.95, ContributingAssertions: []steme.ContributingAssertion{ { AssertionHash: "assertion_1", Weight: 1.0, SourceHash: "source_1", Lifecycle: steme.LifecycleApproved, }, { AssertionHash: "assertion_2", Weight: 0.5, SourceHash: "source_2", Lifecycle: steme.LifecycleProposed, }, }, }, }, TotalCount: 1, AgentID: agentID, FromTimestamp: 1704067200, ToTimestamp: 1704153600, }, nil }, } tool := NewTraceTool(client) input := TraceInput{ AgentID: agentID, From: "1704067200", To: "1704153600", Subject: "Tesla*", } inputBytes, _ := json.Marshal(input) outputBytes, err := tool.Execute(context.Background(), inputBytes) if err != nil { t.Fatalf("trace failed: %v", err) } var output TraceOutput if err := json.Unmarshal(outputBytes, &output); err != nil { t.Fatalf("failed to unmarshal output: %v", err) } // Verify output if len(output.Queries) != 1 { t.Errorf("expected 1 query, got %d", len(output.Queries)) } if len(output.Queries) > 0 { query := output.Queries[0] if query.QueryID != "query_123" { t.Errorf("expected query_id query_123, got %s", query.QueryID) } if query.Subject != "Tesla_Inc" { t.Errorf("expected subject Tesla_Inc, got %s", query.Subject) } if query.Predicate != "has_revenue" { t.Errorf("expected predicate has_revenue, got %s", query.Predicate) } if query.Lens != "consensus" { t.Errorf("expected lens consensus, got %s", query.Lens) } if query.Confidence != 0.95 { t.Errorf("expected confidence 0.95, got %f", query.Confidence) } if len(query.Contributing) != 2 { t.Errorf("expected 2 contributing assertions, got %d", len(query.Contributing)) } } } func TestSupersedeTool(t *testing.T) { // Track what supersession was created var capturedParams steme.SupersedeParams client := &mockClient{ supersedeFunc: func(ctx context.Context, params steme.SupersedeParams) (*steme.SupersedeResult, error) { capturedParams = params return &steme.SupersedeResult{ Status: "superseded", TargetHash: params.TargetHash, SupersessionType: params.SupersessionType, Timestamp: 1704067200, }, nil }, } tool := NewSupersedeTool(client) input := SupersedeInput{ Hash: "abc123def456", Type: "Invalidate", Reason: "Proposal treated as approved. See incident INC-2024-001", NewHash: "def456abc123", AgentID: "deadbeef00000000000000000000000000000000000000000000000000000000", Signature: "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000", } 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("supersede failed: %v", err) } var output SupersedeOutput 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.TargetHash != "abc123def456" { t.Errorf("expected target hash abc123def456, got %s", output.TargetHash) } if output.SupersessionType != "Invalidate" { t.Errorf("expected supersession type Invalidate, got %s", output.SupersessionType) } // Verify captured params if capturedParams.TargetHash != "abc123def456" { t.Errorf("expected target hash abc123def456, got %s", capturedParams.TargetHash) } if capturedParams.SupersessionType != steme.SupersessionInvalidate { t.Errorf("expected supersession type Invalidate, got %s", capturedParams.SupersessionType) } if capturedParams.Reason != "Proposal treated as approved. See incident INC-2024-001" { t.Errorf("expected reason about proposal, got %s", capturedParams.Reason) } } func TestParseSupersessionType(t *testing.T) { tests := []struct { input string expected steme.SupersessionType }{ {"Invalidate", steme.SupersessionInvalidate}, {"invalidate", steme.SupersessionInvalidate}, {"Temporal", steme.SupersessionTemporal}, {"temporal", steme.SupersessionTemporal}, {"Refinement", steme.SupersessionRefinement}, {"RequiresReview", steme.SupersessionRequiresReview}, {"requires_review", steme.SupersessionRequiresReview}, {"Additive", steme.SupersessionAdditive}, {"additive", steme.SupersessionAdditive}, {"unknown", steme.SupersessionInvalidate}, // Default } for _, tt := range tests { result := parseSupersessionType(tt.input) if result != tt.expected { t.Errorf("parseSupersessionType(%s) = %v, expected %v", tt.input, result, tt.expected) } } } func TestToolNames(t *testing.T) { client := &mockClient{} tests := []struct { tool Tool expectedName string }{ {NewQueryTool(client), "episteme_query"}, {NewAssertTool(client), "episteme_assert"}, {NewConstraintCheckTool(client), "episteme_constraint_check"}, {NewTraceTool(client), "episteme_trace"}, {NewSupersedeTool(client), "episteme_supersede"}, } for _, tt := range tests { if tt.tool.Name() != tt.expectedName { t.Errorf("expected tool name %s, got %s", tt.expectedName, tt.tool.Name()) } } } func TestAllTools(t *testing.T) { client := &mockClient{} tools := AllTools(client) if len(tools) != 5 { t.Errorf("expected 5 tools, got %d", len(tools)) } // Verify all expected tools are present expectedNames := map[string]bool{ "episteme_query": false, "episteme_assert": false, "episteme_constraint_check": false, "episteme_trace": false, "episteme_supersede": false, } for _, tool := range tools { expectedNames[tool.Name()] = true } for name, found := range expectedNames { if !found { t.Errorf("expected to find tool %s", name) } } } func TestConfigCreation(t *testing.T) { client := &mockClient{} logFunc := func(format string, args ...any) {} setState := func(key string, value any) {} configs := AllConfigs(client, 0.8, setState, logFunc) expectedAgents := []string{ "implementation", "lead", "research", "supervisor", "oncall", } for _, agentType := range expectedAgents { config, ok := configs[agentType] if !ok { t.Errorf("expected config for %s", agentType) continue } if config.Name == "" { t.Errorf("config for %s has empty name", agentType) } if config.Description == "" { t.Errorf("config for %s has empty description", agentType) } if config.Instruction == "" { t.Errorf("config for %s has empty instruction", agentType) } if len(config.Tools) == 0 { t.Errorf("config for %s has no tools", agentType) } } } func TestParseLifecycle(t *testing.T) { tests := []struct { input string expected steme.LifecycleStage }{ {"proposed", steme.LifecycleProposed}, {"under_review", steme.LifecycleUnderReview}, {"approved", steme.LifecycleApproved}, {"deprecated", steme.LifecycleDeprecated}, {"rejected", steme.LifecycleRejected}, {"invalid", steme.LifecycleProposed}, // Default } for _, tt := range tests { result := parseLifecycle(tt.input) if result != tt.expected { t.Errorf("parseLifecycle(%s) = %v, expected %v", tt.input, result, tt.expected) } } } func TestParseLens(t *testing.T) { tests := []struct { input string expected steme.Lens }{ {"recency", steme.LensRecency}, {"consensus", steme.LensConsensus}, {"authority", steme.LensAuthority}, {"vote_aware_consensus", steme.LensVoteAwareConsensus}, {"trust_aware_authority", steme.LensTrustAwareAuthority}, {"invalid", steme.LensConsensus}, // Default } for _, tt := range tests { result := parseLen(tt.input) if result != tt.expected { t.Errorf("parseLen(%s) = %v, expected %v", tt.input, result, tt.expected) } } }