CI / CI (push) Successful in 4m34s
## Changes - `workflow/synthesis.go` — 5-stage synthesis pipeline as Temporal workflow - `workflow/synthesis_test.go` — 5 tests using Temporal test framework ## Pipeline Stages 1. **ChunkAndEmbed** — chunk text + generate embeddings 2. **ExtractEntities** — LLM entity extraction with reflection 3. **ExtractFacts** — pattern + LLM fact extraction 4. **DetectContradictions** — pre-filter + LLM verification 5. **PersistSynthesis** — save all results to DB
180 lines
5.9 KiB
Go
180 lines
5.9 KiB
Go
package workflow
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"go.temporal.io/sdk/testsuite"
|
|
)
|
|
|
|
// Stub activity functions for test registration
|
|
func ChunkAndEmbedActivity(_ context.Context, _ SynthesisInput) (string, error) { return "", nil }
|
|
func ExtractEntitiesActivity(_ context.Context, _ string, _ string) ([]ExtractedEntity, error) { return nil, nil }
|
|
func ExtractFactsActivity(_ context.Context, _ string, _ string, _ []ExtractedEntity) ([]ExtractedFact, error) { return nil, nil }
|
|
func DetectContradictionsActivity(_ context.Context, _ string, _ []ExtractedFact) ([]ContradictionResult, error) { return nil, nil }
|
|
func PersistSynthesisActivity(_ context.Context, _ PersistInput) error { return nil }
|
|
|
|
func registerSynthesisActivities(env *testsuite.TestWorkflowEnvironment) {
|
|
env.RegisterActivity(ChunkAndEmbedActivity)
|
|
env.RegisterActivity(ExtractEntitiesActivity)
|
|
env.RegisterActivity(ExtractFactsActivity)
|
|
env.RegisterActivity(DetectContradictionsActivity)
|
|
env.RegisterActivity(PersistSynthesisActivity)
|
|
}
|
|
|
|
func TestSynthesisWorkflow_Success(t *testing.T) {
|
|
ts := &testsuite.WorkflowTestSuite{}
|
|
env := ts.NewTestWorkflowEnvironment()
|
|
|
|
input := SynthesisInput{
|
|
Project: "poimen",
|
|
Source: "transcript://test-123",
|
|
Text: "Kubernetes uses port 8080 for the API server",
|
|
Kind: "L1",
|
|
}
|
|
|
|
registerSynthesisActivities(env)
|
|
|
|
// Stage 1: Chunk + Embed
|
|
env.OnActivity(ChunkAndEmbedActivity, mock.Anything, input).Return("chunk-abc123", nil)
|
|
|
|
// Stage 2: Entity Extraction
|
|
entities := []ExtractedEntity{
|
|
{Name: "Kubernetes", EntityType: "tool", Confidence: 0.95},
|
|
{Name: "API server", EntityType: "component", Confidence: 0.90},
|
|
}
|
|
env.OnActivity(ExtractEntitiesActivity, mock.Anything, "chunk-abc123", input.Text).Return(entities, nil)
|
|
|
|
// Stage 3: Fact Extraction
|
|
facts := []ExtractedFact{
|
|
{Subject: "Kubernetes", Predicate: "uses_port", Object: "8080", Confidence: 0.85},
|
|
}
|
|
env.OnActivity(ExtractFactsActivity, mock.Anything, "chunk-abc123", input.Text, entities).Return(facts, nil)
|
|
|
|
// Stage 4: Contradiction Detection
|
|
contradictions := []ContradictionResult{}
|
|
env.OnActivity(DetectContradictionsActivity, mock.Anything, "poimen", facts).Return(contradictions, nil)
|
|
|
|
// Stage 5: Persist
|
|
env.OnActivity(PersistSynthesisActivity, mock.Anything, mock.Anything).Return(nil)
|
|
|
|
env.ExecuteWorkflow(SynthesisWorkflow, input)
|
|
|
|
assert.True(t, env.IsWorkflowCompleted())
|
|
assert.NoError(t, env.GetWorkflowError())
|
|
|
|
var result SynthesisResult
|
|
assert.NoError(t, env.GetWorkflowResult(&result))
|
|
assert.Equal(t, "chunk-abc123", result.ChunkID)
|
|
assert.Equal(t, 2, result.EntitiesExtracted)
|
|
assert.Equal(t, 1, result.FactsExtracted)
|
|
assert.Equal(t, 0, result.Contradictions)
|
|
assert.Equal(t, 0, result.ReviewQueued)
|
|
}
|
|
|
|
func TestSynthesisWorkflow_WithContradictions(t *testing.T) {
|
|
ts := &testsuite.WorkflowTestSuite{}
|
|
env := ts.NewTestWorkflowEnvironment()
|
|
registerSynthesisActivities(env)
|
|
|
|
input := SynthesisInput{
|
|
Project: "poimen",
|
|
Source: "transcript://test-456",
|
|
Text: "Port 8080 is used by nginx",
|
|
Kind: "L1",
|
|
}
|
|
|
|
env.OnActivity(ChunkAndEmbedActivity, mock.Anything, input).Return("chunk-def456", nil)
|
|
|
|
entities := []ExtractedEntity{
|
|
{Name: "nginx", EntityType: "tool", Confidence: 0.92},
|
|
}
|
|
env.OnActivity(ExtractEntitiesActivity, mock.Anything, "chunk-def456", input.Text).Return(entities, nil)
|
|
|
|
facts := []ExtractedFact{
|
|
{Subject: "nginx", Predicate: "uses_port", Object: "8080", Confidence: 0.88},
|
|
}
|
|
env.OnActivity(ExtractFactsActivity, mock.Anything, "chunk-def456", input.Text, entities).Return(facts, nil)
|
|
|
|
contradictions := []ContradictionResult{
|
|
{
|
|
FactA: ExtractedFact{Subject: "Kubernetes", Predicate: "uses_port", Object: "8080"},
|
|
FactB: ExtractedFact{Subject: "nginx", Predicate: "uses_port", Object: "8080"},
|
|
Severity: "medium",
|
|
AutoResolved: false,
|
|
QueuedReview: true,
|
|
},
|
|
}
|
|
env.OnActivity(DetectContradictionsActivity, mock.Anything, "poimen", facts).Return(contradictions, nil)
|
|
env.OnActivity(PersistSynthesisActivity, mock.Anything, mock.Anything).Return(nil)
|
|
|
|
env.ExecuteWorkflow(SynthesisWorkflow, input)
|
|
|
|
assert.True(t, env.IsWorkflowCompleted())
|
|
assert.NoError(t, env.GetWorkflowError())
|
|
|
|
var result SynthesisResult
|
|
assert.NoError(t, env.GetWorkflowResult(&result))
|
|
assert.Equal(t, 1, result.Contradictions)
|
|
assert.Equal(t, 1, result.ReviewQueued)
|
|
}
|
|
|
|
func TestSynthesisWorkflow_EntityExtractionFails(t *testing.T) {
|
|
ts := &testsuite.WorkflowTestSuite{}
|
|
env := ts.NewTestWorkflowEnvironment()
|
|
registerSynthesisActivities(env)
|
|
|
|
input := SynthesisInput{
|
|
Project: "poimen",
|
|
Source: "transcript://test-789",
|
|
Text: "Some text",
|
|
Kind: "L1",
|
|
}
|
|
|
|
env.OnActivity(ChunkAndEmbedActivity, mock.Anything, input).Return("chunk-xyz", nil)
|
|
env.OnActivity(ExtractEntitiesActivity, mock.Anything, "chunk-xyz", input.Text).
|
|
Return(nil, assert.AnError)
|
|
|
|
env.ExecuteWorkflow(SynthesisWorkflow, input)
|
|
|
|
assert.True(t, env.IsWorkflowCompleted())
|
|
assert.Error(t, env.GetWorkflowError())
|
|
assert.Contains(t, env.GetWorkflowError().Error(), "stage 2 entity extraction")
|
|
}
|
|
|
|
func TestSynthesisWorkflow_ChunkFails(t *testing.T) {
|
|
ts := &testsuite.WorkflowTestSuite{}
|
|
env := ts.NewTestWorkflowEnvironment()
|
|
registerSynthesisActivities(env)
|
|
|
|
input := SynthesisInput{
|
|
Project: "poimen",
|
|
Source: "transcript://test-fail",
|
|
Text: "Bad text",
|
|
Kind: "L1",
|
|
}
|
|
|
|
env.OnActivity(ChunkAndEmbedActivity, mock.Anything, input).Return("", assert.AnError)
|
|
|
|
env.ExecuteWorkflow(SynthesisWorkflow, input)
|
|
|
|
assert.True(t, env.IsWorkflowCompleted())
|
|
assert.Error(t, env.GetWorkflowError())
|
|
assert.Contains(t, env.GetWorkflowError().Error(), "stage 1 chunk+embed")
|
|
}
|
|
|
|
func TestSynthesisInput_Fields(t *testing.T) {
|
|
input := SynthesisInput{
|
|
Project: "test",
|
|
Source: "source://1",
|
|
Text: "hello",
|
|
Kind: "L2",
|
|
Tags: []string{"tag1", "tag2"},
|
|
}
|
|
assert.Equal(t, "test", input.Project)
|
|
assert.Equal(t, "L2", input.Kind)
|
|
assert.Len(t, input.Tags, 2)
|
|
}
|