- Implement 12 Temporal activities for memory operations - Activities: create, update, search, context, diagnose, analyze, document - Add activity registration and worker setup - Full retry/timeout configuration with observability - Include workflow patterns and examples - All tests passing (23/23) Documentation: - MEMORY_INTEGRATION.md: High-level integration guide - MEMORY_ACTIVITIES.md: Complete activities reference - REGISTERED_ACTIVITIES.md: Registry and calling conventions
255 lines
5.7 KiB
Go
255 lines
5.7 KiB
Go
package memory
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestServiceCreateKnowledge(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/memory/ingest" {
|
|
t.Errorf("unexpected path: %s", r.URL.Path)
|
|
}
|
|
|
|
var req IngestRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
t.Fatalf("decode request: %v", err)
|
|
}
|
|
|
|
if req.Project != "poimen" {
|
|
t.Errorf("expected project poimen, got %s", req.Project)
|
|
}
|
|
|
|
w.WriteHeader(http.StatusCreated)
|
|
json.NewEncoder(w).Encode(IngestResponse{
|
|
ID: "chunk-123",
|
|
QueueStatus: "pending",
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
svc := NewService(server.URL, "test-token", "poimen")
|
|
id, err := svc.CreateKnowledge(context.Background(), &KnowledgeRecord{
|
|
Content: "test knowledge",
|
|
Level: "L1",
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("create knowledge failed: %v", err)
|
|
}
|
|
|
|
if id != "chunk-123" {
|
|
t.Errorf("expected ID chunk-123, got %s", id)
|
|
}
|
|
}
|
|
|
|
func TestServiceRetrieveKnowledge(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/memory/query" {
|
|
t.Errorf("unexpected path: %s", r.URL.Path)
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(QueryResponse{
|
|
Query: "test",
|
|
Results: []QueryResult{
|
|
{
|
|
ID: "chunk-123",
|
|
Level: "L1",
|
|
Score: 0.95,
|
|
SemanticScore: 0.96,
|
|
LexicalScore: 0.94,
|
|
Text: "knowledge content",
|
|
Breadcrumb: "path > to > doc",
|
|
Source: "test",
|
|
},
|
|
},
|
|
TotalHits: 1,
|
|
SearchTimeMS: 50,
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
svc := NewService(server.URL, "test-token", "poimen")
|
|
records, err := svc.RetrieveKnowledge(context.Background(), "test", nil)
|
|
|
|
if err != nil {
|
|
t.Fatalf("retrieve knowledge failed: %v", err)
|
|
}
|
|
|
|
if len(records) != 1 {
|
|
t.Errorf("expected 1 record, got %d", len(records))
|
|
}
|
|
|
|
if records[0].Content != "knowledge content" {
|
|
t.Errorf("unexpected content")
|
|
}
|
|
|
|
meta := records[0].Metadata
|
|
if score, ok := meta["score"].(float32); ok {
|
|
if score != 0.95 {
|
|
t.Errorf("expected score 0.95, got %f", score)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestServiceRetrieveContext(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/memory/context" {
|
|
t.Errorf("unexpected path: %s", r.URL.Path)
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(ContextResponse{
|
|
Tier: 1,
|
|
Lessons: []ContextLesson{
|
|
{
|
|
Tier: 1,
|
|
Level: "L1",
|
|
Score: 1.0,
|
|
Text: "first lesson",
|
|
MatchedKind: "signature",
|
|
},
|
|
{
|
|
Tier: 2,
|
|
Level: "L2",
|
|
Score: 0.87,
|
|
Text: "second lesson",
|
|
},
|
|
},
|
|
Skills: []ContextSkill{
|
|
{
|
|
Name: "debug-skill",
|
|
Why: "tier 1 matched",
|
|
},
|
|
},
|
|
Budget: ContextBudget{
|
|
Requested: 8192,
|
|
Used: 2048,
|
|
Dropped: 0,
|
|
},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
svc := NewService(server.URL, "test-token", "poimen")
|
|
ctx, err := svc.RetrieveContext(context.Background(), "kubectl", "debug-pod", 8192)
|
|
|
|
if err != nil {
|
|
t.Fatalf("retrieve context failed: %v", err)
|
|
}
|
|
|
|
if ctx.Tier != 1 {
|
|
t.Errorf("expected tier 1, got %d", ctx.Tier)
|
|
}
|
|
|
|
if len(ctx.Lessons) != 2 {
|
|
t.Errorf("expected 2 lessons, got %d", len(ctx.Lessons))
|
|
}
|
|
|
|
if len(ctx.Skills) != 1 {
|
|
t.Errorf("expected 1 skill, got %d", len(ctx.Skills))
|
|
}
|
|
|
|
if ctx.BudgetUsed != 2048 {
|
|
t.Errorf("expected budget used 2048, got %d", ctx.BudgetUsed)
|
|
}
|
|
}
|
|
|
|
func TestServiceGetVault(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/memory/vault" {
|
|
t.Errorf("unexpected path: %s", r.URL.Path)
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(VaultResponse{
|
|
Project: "poimen",
|
|
TotalRecords: 100,
|
|
Files: []VaultFile{
|
|
{
|
|
Path: "docs/guide.md",
|
|
Title: "Guide",
|
|
Level: "L1",
|
|
RecordCount: 25,
|
|
},
|
|
{
|
|
Path: "reference/api.md",
|
|
Title: "API",
|
|
Level: "R",
|
|
RecordCount: 75,
|
|
},
|
|
},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
svc := NewService(server.URL, "test-token", "poimen")
|
|
files, err := svc.GetVault(context.Background())
|
|
|
|
if err != nil {
|
|
t.Fatalf("get vault failed: %v", err)
|
|
}
|
|
|
|
if len(files) != 2 {
|
|
t.Errorf("expected 2 files, got %d", len(files))
|
|
}
|
|
|
|
if files[0].Title != "Guide" {
|
|
t.Errorf("expected title Guide, got %s", files[0].Title)
|
|
}
|
|
}
|
|
|
|
func TestServiceIsHealthy(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer server.Close()
|
|
|
|
svc := NewService(server.URL, "test-token", "poimen")
|
|
if !svc.IsHealthy(context.Background()) {
|
|
t.Error("expected service to be healthy")
|
|
}
|
|
}
|
|
|
|
func TestServiceUpdateKnowledge(t *testing.T) {
|
|
callCount := 0
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
callCount++
|
|
if r.URL.Path != "/memory/ingest" {
|
|
t.Errorf("unexpected path: %s", r.URL.Path)
|
|
}
|
|
|
|
w.WriteHeader(http.StatusCreated)
|
|
json.NewEncoder(w).Encode(IngestResponse{
|
|
ID: "chunk-123",
|
|
QueueStatus: "pending",
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
svc := NewService(server.URL, "test-token", "poimen")
|
|
record := &KnowledgeRecord{
|
|
ID: "chunk-123",
|
|
Content: "updated knowledge",
|
|
Level: "L1",
|
|
}
|
|
|
|
id, err := svc.UpdateKnowledge(context.Background(), record)
|
|
|
|
if err != nil {
|
|
t.Fatalf("update knowledge failed: %v", err)
|
|
}
|
|
|
|
if id != "chunk-123" {
|
|
t.Errorf("expected ID chunk-123, got %s", id)
|
|
}
|
|
|
|
if callCount != 1 {
|
|
t.Errorf("expected 1 call, got %d", callCount)
|
|
}
|
|
}
|