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>
303 lines
7.3 KiB
Go
303 lines
7.3 KiB
Go
//go:build integration
|
|
|
|
package steme
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Integration tests for Assert operations including object types and validation.
|
|
|
|
// TestIntegration_Assert_Query_Roundtrip tests creating an assertion and querying it back.
|
|
func TestIntegration_Assert_Query_Roundtrip(t *testing.T) {
|
|
client := getTestClient(t)
|
|
ctx := context.Background()
|
|
|
|
// Create a unique subject to avoid interference
|
|
subject := uniqueSubject("IntegrationTest_Entity")
|
|
predicate := "has_value"
|
|
expectedValue := "test_value_roundtrip"
|
|
|
|
// Create assertion
|
|
assertion := NewAssertion(subject, predicate).
|
|
WithText(expectedValue).
|
|
WithConfidence(0.95).
|
|
WithLifecycle(LifecycleApproved).
|
|
WithSourceClass(SourceClassClinical).
|
|
WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
|
|
Build()
|
|
|
|
hash, err := client.Assert(ctx, assertion)
|
|
if err != nil {
|
|
t.Fatalf("Assert() failed: %v", err)
|
|
}
|
|
|
|
if len(hash) != 64 {
|
|
t.Errorf("Assert() hash length = %d, want 64", len(hash))
|
|
}
|
|
|
|
t.Logf("Created assertion with hash: %s", hash)
|
|
|
|
// Small delay to ensure indexing completes
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Query it back
|
|
params := NewQuery().
|
|
WithSubject(subject).
|
|
WithPredicate(predicate).
|
|
Build()
|
|
|
|
result, err := client.Query(ctx, params)
|
|
if err != nil {
|
|
t.Fatalf("Query() failed: %v", err)
|
|
}
|
|
|
|
if result.TotalCount == 0 {
|
|
t.Fatal("Query() returned no results")
|
|
}
|
|
|
|
// Verify we got our assertion back
|
|
found := false
|
|
for _, a := range result.Assertions {
|
|
if a.Hash == hash {
|
|
found = true
|
|
|
|
if a.Subject != subject {
|
|
t.Errorf("Subject = %s, want %s", a.Subject, subject)
|
|
}
|
|
|
|
if a.Predicate != predicate {
|
|
t.Errorf("Predicate = %s, want %s", a.Predicate, predicate)
|
|
}
|
|
|
|
if a.Object.Type != "Text" {
|
|
t.Errorf("Object.Type = %s, want Text", a.Object.Type)
|
|
}
|
|
|
|
if a.Object.Value != expectedValue {
|
|
t.Errorf("Object.Value = %v, want %s", a.Object.Value, expectedValue)
|
|
}
|
|
|
|
if a.Confidence != 0.95 {
|
|
t.Errorf("Confidence = %f, want 0.95", a.Confidence)
|
|
}
|
|
|
|
if a.Lifecycle != LifecycleApproved {
|
|
t.Errorf("Lifecycle = %s, want Approved", a.Lifecycle)
|
|
}
|
|
|
|
if a.SourceClass != SourceClassClinical {
|
|
t.Errorf("SourceClass = %s, want Clinical", a.SourceClass)
|
|
}
|
|
|
|
if len(a.Signatures) == 0 {
|
|
t.Error("Assertion has no signatures")
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
t.Errorf("Did not find assertion with hash %s in query results", hash)
|
|
}
|
|
}
|
|
|
|
// TestIntegration_Assert_AllObjectTypes tests all object value types.
|
|
func TestIntegration_Assert_AllObjectTypes(t *testing.T) {
|
|
client := getTestClient(t)
|
|
ctx := context.Background()
|
|
|
|
subject := uniqueSubject("IntegrationTest_Types")
|
|
|
|
tests := []struct {
|
|
name string
|
|
predicate string
|
|
builder func(*AssertionBuilder) *AssertionBuilder
|
|
checkType string
|
|
}{
|
|
{
|
|
name: "Text",
|
|
predicate: "has_text",
|
|
builder: func(b *AssertionBuilder) *AssertionBuilder {
|
|
return b.WithText("hello world")
|
|
},
|
|
checkType: "Text",
|
|
},
|
|
{
|
|
name: "Number",
|
|
predicate: "has_number",
|
|
builder: func(b *AssertionBuilder) *AssertionBuilder {
|
|
return b.WithNumber(42.5)
|
|
},
|
|
checkType: "Number",
|
|
},
|
|
{
|
|
name: "Boolean",
|
|
predicate: "has_boolean",
|
|
builder: func(b *AssertionBuilder) *AssertionBuilder {
|
|
return b.WithBoolean(true)
|
|
},
|
|
checkType: "Boolean",
|
|
},
|
|
{
|
|
name: "Reference",
|
|
predicate: "has_reference",
|
|
builder: func(b *AssertionBuilder) *AssertionBuilder {
|
|
return b.WithReference("Entity_123")
|
|
},
|
|
checkType: "Reference",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
builder := NewAssertion(subject, tt.predicate).
|
|
WithConfidence(0.9).
|
|
WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000")
|
|
|
|
builder = tt.builder(builder)
|
|
assertion := builder.Build()
|
|
|
|
hash, err := client.Assert(ctx, assertion)
|
|
if err != nil {
|
|
t.Fatalf("Assert() for %s failed: %v", tt.name, err)
|
|
}
|
|
|
|
t.Logf("Created %s assertion with hash: %s", tt.name, hash)
|
|
|
|
// Small delay for indexing
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Query it back
|
|
params := NewQuery().
|
|
WithSubject(subject).
|
|
WithPredicate(tt.predicate).
|
|
Build()
|
|
|
|
result, err := client.Query(ctx, params)
|
|
if err != nil {
|
|
t.Fatalf("Query() for %s failed: %v", tt.name, err)
|
|
}
|
|
|
|
if result.TotalCount == 0 {
|
|
t.Fatalf("Query() for %s returned no results", tt.name)
|
|
}
|
|
|
|
// Verify object type
|
|
if result.Assertions[0].Object.Type != tt.checkType {
|
|
t.Errorf("Object.Type = %s, want %s", result.Assertions[0].Object.Type, tt.checkType)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIntegration_Assert_InvalidAssertion tests that invalid assertions are rejected.
|
|
func TestIntegration_Assert_InvalidAssertion(t *testing.T) {
|
|
client := getTestClient(t)
|
|
ctx := context.Background()
|
|
|
|
tests := []struct {
|
|
name string
|
|
assertion Assertion
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "missing source_hash",
|
|
assertion: NewAssertion("Subject", "Predicate").
|
|
WithText("value").
|
|
Build(),
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid confidence",
|
|
assertion: NewAssertion("Subject", "Predicate").
|
|
WithText("value").
|
|
WithConfidence(1.5).
|
|
WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
|
|
Build(),
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "negative confidence",
|
|
assertion: NewAssertion("Subject", "Predicate").
|
|
WithText("value").
|
|
WithConfidence(-0.1).
|
|
WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
|
|
Build(),
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, err := client.Assert(ctx, tt.assertion)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Assert() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
|
|
if tt.wantErr && err != nil {
|
|
t.Logf("Got expected error: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIntegration_Lifecycle tests lifecycle stage filtering.
|
|
func TestIntegration_Lifecycle(t *testing.T) {
|
|
client := getTestClient(t)
|
|
ctx := context.Background()
|
|
|
|
subject := uniqueSubject("IntegrationTest_Lifecycle")
|
|
predicate := "has_status"
|
|
|
|
// Create assertions with different lifecycle stages
|
|
stages := []LifecycleStage{
|
|
LifecycleProposed,
|
|
LifecycleApproved,
|
|
LifecycleDeprecated,
|
|
}
|
|
|
|
for _, stage := range stages {
|
|
assertion := NewAssertion(subject, predicate).
|
|
WithText(string(stage)).
|
|
WithLifecycle(stage).
|
|
WithConfidence(0.9).
|
|
WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
|
|
Build()
|
|
|
|
hash, err := client.Assert(ctx, assertion)
|
|
if err != nil {
|
|
t.Fatalf("Assert() for lifecycle %s failed: %v", stage, err)
|
|
}
|
|
|
|
t.Logf("Created assertion with lifecycle %s: %s", stage, hash)
|
|
}
|
|
|
|
// Small delay for indexing
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Query for only Approved lifecycle
|
|
params := NewQuery().
|
|
WithSubject(subject).
|
|
WithPredicate(predicate).
|
|
WithLifecycle(LifecycleApproved).
|
|
Build()
|
|
|
|
result, err := client.Query(ctx, params)
|
|
if err != nil {
|
|
t.Fatalf("Query() failed: %v", err)
|
|
}
|
|
|
|
// Should only get the Approved assertion
|
|
for _, a := range result.Assertions {
|
|
if a.Lifecycle != LifecycleApproved {
|
|
t.Errorf("Got assertion with lifecycle %s, want Approved only", a.Lifecycle)
|
|
}
|
|
}
|
|
|
|
t.Logf("Lifecycle filtering returned %d Approved assertions", result.TotalCount)
|
|
}
|