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>
204 lines
5.4 KiB
Go
204 lines
5.4 KiB
Go
//go:build integration
|
|
|
|
package steme
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Integration tests for Trace/audit operations.
|
|
|
|
// TestIntegration_Trace tests the trace endpoint for audit trail querying.
|
|
func TestIntegration_Trace(t *testing.T) {
|
|
client := getTestClient(t)
|
|
ctx := context.Background()
|
|
|
|
// Create a unique subject for this test
|
|
subject := uniqueSubject("IntegrationTest_Trace")
|
|
predicate := "has_trace_value"
|
|
|
|
// Create an assertion to generate query activity
|
|
assertion := NewAssertion(subject, predicate).
|
|
WithText("test_trace_value").
|
|
WithConfidence(0.95).
|
|
WithLifecycle(LifecycleApproved).
|
|
WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
|
|
Build()
|
|
|
|
hash, err := client.Assert(ctx, assertion)
|
|
if err != nil {
|
|
t.Fatalf("Assert() failed: %v", err)
|
|
}
|
|
|
|
t.Logf("Created assertion with hash: %s", hash)
|
|
|
|
// Small delay for indexing
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Perform a query to create audit trail
|
|
params := NewQuery().
|
|
WithSubject(subject).
|
|
WithPredicate(predicate).
|
|
Build()
|
|
|
|
queryResult, err := client.Query(ctx, params)
|
|
if err != nil {
|
|
t.Fatalf("Query() failed: %v", err)
|
|
}
|
|
|
|
if queryResult.TotalCount == 0 {
|
|
t.Fatal("Query() returned no results")
|
|
}
|
|
|
|
t.Logf("Query returned %d results", queryResult.TotalCount)
|
|
|
|
// Small delay for audit to be written
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Get the agent ID from the client's signer
|
|
agentID := client.signer.PublicKey()
|
|
|
|
// Calculate time range (last 1 hour to now)
|
|
now := time.Now().Unix()
|
|
from := now - 3600
|
|
|
|
// Trace the agent's queries
|
|
traceParams := TraceParams{
|
|
AgentID: agentID,
|
|
From: fmt.Sprintf("%d", from),
|
|
To: fmt.Sprintf("%d", now),
|
|
Subject: subject, // Filter to our test subject
|
|
Limit: 100,
|
|
}
|
|
|
|
traceResult, err := client.Trace(ctx, traceParams)
|
|
if err != nil {
|
|
t.Fatalf("Trace() failed: %v", err)
|
|
}
|
|
|
|
t.Logf("Trace returned %d audit records", traceResult.TotalCount)
|
|
|
|
// Verify we got at least one audit record
|
|
if traceResult.TotalCount == 0 {
|
|
t.Error("Trace() returned no audit records")
|
|
}
|
|
|
|
// Verify the trace result has the correct agent ID
|
|
if traceResult.AgentID != agentID {
|
|
t.Errorf("TraceResult.AgentID = %s, want %s", traceResult.AgentID, agentID)
|
|
}
|
|
|
|
// Verify the trace result has the correct time range
|
|
if traceResult.FromTimestamp != uint64(from) {
|
|
t.Errorf("TraceResult.FromTimestamp = %d, want %d", traceResult.FromTimestamp, from)
|
|
}
|
|
|
|
// Verify audit records contain our query
|
|
foundOurQuery := false
|
|
for _, audit := range traceResult.Audits {
|
|
t.Logf("Audit record: query_id=%s, timestamp=%d, subject=%v, predicate=%v",
|
|
audit.QueryID, audit.Timestamp, audit.Params.Subject, audit.Params.Predicate)
|
|
|
|
// Check if this audit is for our query
|
|
if audit.Params.Subject != nil && *audit.Params.Subject == subject &&
|
|
audit.Params.Predicate != nil && *audit.Params.Predicate == predicate {
|
|
foundOurQuery = true
|
|
|
|
// Verify agent ID
|
|
if audit.AgentID == nil || *audit.AgentID != agentID {
|
|
t.Errorf("Audit.AgentID = %v, want %s", audit.AgentID, agentID)
|
|
}
|
|
|
|
// Verify result hash is present
|
|
if audit.ResultHash == nil {
|
|
t.Error("Audit.ResultHash is nil, expected a result")
|
|
}
|
|
|
|
// Verify contributing assertions
|
|
if len(audit.ContributingAssertions) == 0 {
|
|
t.Error("Audit.ContributingAssertions is empty, expected at least one")
|
|
}
|
|
}
|
|
}
|
|
|
|
if !foundOurQuery {
|
|
t.Errorf("Did not find audit record for our query (subject=%s, predicate=%s)", subject, predicate)
|
|
}
|
|
}
|
|
|
|
// TestIntegration_Trace_WithPattern tests trace with subject pattern filtering.
|
|
func TestIntegration_Trace_WithPattern(t *testing.T) {
|
|
client := getTestClient(t)
|
|
ctx := context.Background()
|
|
|
|
// Create unique subjects with a common prefix
|
|
prefix := uniqueSubject("IntegrationTest_TracePattern")
|
|
subject1 := prefix + "_A"
|
|
subject2 := prefix + "_B"
|
|
predicate := "has_value"
|
|
|
|
// Create assertions and query them
|
|
for _, subj := range []string{subject1, subject2} {
|
|
assertion := NewAssertion(subj, predicate).
|
|
WithText("test_value").
|
|
WithConfidence(0.9).
|
|
WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
|
|
Build()
|
|
|
|
_, err := client.Assert(ctx, assertion)
|
|
if err != nil {
|
|
t.Fatalf("Assert() for %s failed: %v", subj, err)
|
|
}
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Query to create audit trail
|
|
params := NewQuery().WithSubject(subj).WithPredicate(predicate).Build()
|
|
_, err = client.Query(ctx, params)
|
|
if err != nil {
|
|
t.Fatalf("Query() for %s failed: %v", subj, err)
|
|
}
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Trace with wildcard pattern
|
|
agentID := client.signer.PublicKey()
|
|
now := time.Now().Unix()
|
|
from := now - 3600
|
|
|
|
traceParams := TraceParams{
|
|
AgentID: agentID,
|
|
From: fmt.Sprintf("%d", from),
|
|
To: fmt.Sprintf("%d", now),
|
|
Subject: prefix + "*", // Match both subjects
|
|
Limit: 100,
|
|
}
|
|
|
|
traceResult, err := client.Trace(ctx, traceParams)
|
|
if err != nil {
|
|
t.Fatalf("Trace() failed: %v", err)
|
|
}
|
|
|
|
t.Logf("Trace with pattern returned %d audit records", traceResult.TotalCount)
|
|
|
|
// Should find both queries
|
|
foundSubjects := make(map[string]bool)
|
|
for _, audit := range traceResult.Audits {
|
|
if audit.Params.Subject != nil {
|
|
foundSubjects[*audit.Params.Subject] = true
|
|
}
|
|
}
|
|
|
|
if !foundSubjects[subject1] {
|
|
t.Errorf("Did not find audit for subject %s", subject1)
|
|
}
|
|
|
|
if !foundSubjects[subject2] {
|
|
t.Errorf("Did not find audit for subject %s", subject2)
|
|
}
|
|
}
|