Implement UCXL Protocol Foundation (Phase 1)
- Add complete UCXL address parser with BNF grammar validation - Implement temporal navigation system with bounds checking - Create UCXI HTTP server with REST-like operations - Add comprehensive test suite with 87 passing tests - Integrate with existing BZZZ architecture (opt-in via config) - Support semantic addressing with wildcards and version control Core Features: - UCXL address format: ucxl://agent:role@project:task/temporal/path - Temporal segments: *^, ~~N, ^^N, *~, *~N with navigation logic - UCXI endpoints: GET/PUT/POST/DELETE/ANNOUNCE operations - Production-ready with error handling and graceful shutdown 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
623
pkg/ucxl/temporal_test.go
Normal file
623
pkg/ucxl/temporal_test.go
Normal file
@@ -0,0 +1,623 @@
|
||||
package ucxl
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewTemporalNavigator(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
maxVersion int
|
||||
expectedMax int
|
||||
expectedCurrent int
|
||||
}{
|
||||
{
|
||||
name: "positive max version",
|
||||
maxVersion: 10,
|
||||
expectedMax: 10,
|
||||
expectedCurrent: 10,
|
||||
},
|
||||
{
|
||||
name: "zero max version",
|
||||
maxVersion: 0,
|
||||
expectedMax: 0,
|
||||
expectedCurrent: 0,
|
||||
},
|
||||
{
|
||||
name: "negative max version defaults to 0",
|
||||
maxVersion: -5,
|
||||
expectedMax: 0,
|
||||
expectedCurrent: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
nav := NewTemporalNavigator(tt.maxVersion)
|
||||
|
||||
if nav.GetMaxVersion() != tt.expectedMax {
|
||||
t.Errorf("GetMaxVersion() = %d, want %d", nav.GetMaxVersion(), tt.expectedMax)
|
||||
}
|
||||
|
||||
if nav.GetCurrentVersion() != tt.expectedCurrent {
|
||||
t.Errorf("GetCurrentVersion() = %d, want %d", nav.GetCurrentVersion(), tt.expectedCurrent)
|
||||
}
|
||||
|
||||
if nav.GetHistory() == nil {
|
||||
t.Error("History should be initialized")
|
||||
}
|
||||
|
||||
if len(nav.GetHistory()) != 0 {
|
||||
t.Error("History should be empty initially")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNavigateLatest(t *testing.T) {
|
||||
nav := NewTemporalNavigator(10)
|
||||
|
||||
// Navigate to version 5 first
|
||||
nav.currentVersion = 5
|
||||
|
||||
segment := TemporalSegment{Type: TemporalLatest}
|
||||
result, err := nav.Navigate(segment)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Navigate() error = %v, want nil", err)
|
||||
}
|
||||
|
||||
if !result.Success {
|
||||
t.Error("Navigation should be successful")
|
||||
}
|
||||
|
||||
if result.TargetVersion != 10 {
|
||||
t.Errorf("TargetVersion = %d, want 10", result.TargetVersion)
|
||||
}
|
||||
|
||||
if result.PreviousVersion != 5 {
|
||||
t.Errorf("PreviousVersion = %d, want 5", result.PreviousVersion)
|
||||
}
|
||||
|
||||
if nav.GetCurrentVersion() != 10 {
|
||||
t.Errorf("Current version = %d, want 10", nav.GetCurrentVersion())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNavigateAny(t *testing.T) {
|
||||
nav := NewTemporalNavigator(10)
|
||||
nav.currentVersion = 5
|
||||
|
||||
segment := TemporalSegment{Type: TemporalAny}
|
||||
result, err := nav.Navigate(segment)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Navigate() error = %v, want nil", err)
|
||||
}
|
||||
|
||||
if !result.Success {
|
||||
t.Error("Navigation should be successful")
|
||||
}
|
||||
|
||||
if result.TargetVersion != 5 {
|
||||
t.Errorf("TargetVersion = %d, want 5 (should stay at current)", result.TargetVersion)
|
||||
}
|
||||
|
||||
if nav.GetCurrentVersion() != 5 {
|
||||
t.Errorf("Current version = %d, want 5", nav.GetCurrentVersion())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNavigateSpecific(t *testing.T) {
|
||||
nav := NewTemporalNavigator(10)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
version int
|
||||
shouldError bool
|
||||
expectedPos int
|
||||
}{
|
||||
{
|
||||
name: "valid version",
|
||||
version: 7,
|
||||
shouldError: false,
|
||||
expectedPos: 7,
|
||||
},
|
||||
{
|
||||
name: "version 0",
|
||||
version: 0,
|
||||
shouldError: false,
|
||||
expectedPos: 0,
|
||||
},
|
||||
{
|
||||
name: "max version",
|
||||
version: 10,
|
||||
shouldError: false,
|
||||
expectedPos: 10,
|
||||
},
|
||||
{
|
||||
name: "negative version",
|
||||
version: -1,
|
||||
shouldError: true,
|
||||
expectedPos: 10, // Should stay at original position
|
||||
},
|
||||
{
|
||||
name: "version beyond max",
|
||||
version: 15,
|
||||
shouldError: true,
|
||||
expectedPos: 10, // Should stay at original position
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
nav.Reset() // Reset to max version
|
||||
|
||||
segment := TemporalSegment{
|
||||
Type: TemporalSpecific,
|
||||
Count: tt.version,
|
||||
}
|
||||
|
||||
result, err := nav.Navigate(segment)
|
||||
|
||||
if tt.shouldError {
|
||||
if err == nil {
|
||||
t.Error("Expected error but got none")
|
||||
}
|
||||
if result.Success {
|
||||
t.Error("Result should indicate failure")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Error("Result should indicate success")
|
||||
}
|
||||
}
|
||||
|
||||
if nav.GetCurrentVersion() != tt.expectedPos {
|
||||
t.Errorf("Current version = %d, want %d", nav.GetCurrentVersion(), tt.expectedPos)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNavigateBackward(t *testing.T) {
|
||||
nav := NewTemporalNavigator(10)
|
||||
nav.currentVersion = 5
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
count int
|
||||
shouldError bool
|
||||
expectedPos int
|
||||
}{
|
||||
{
|
||||
name: "valid backward navigation",
|
||||
count: 2,
|
||||
shouldError: false,
|
||||
expectedPos: 3,
|
||||
},
|
||||
{
|
||||
name: "backward to version 0",
|
||||
count: 5,
|
||||
shouldError: false,
|
||||
expectedPos: 0,
|
||||
},
|
||||
{
|
||||
name: "backward beyond version 0",
|
||||
count: 10,
|
||||
shouldError: true,
|
||||
expectedPos: 5, // Should stay at original position
|
||||
},
|
||||
{
|
||||
name: "negative count",
|
||||
count: -1,
|
||||
shouldError: true,
|
||||
expectedPos: 5, // Should stay at original position
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
nav.currentVersion = 5 // Reset position
|
||||
|
||||
segment := TemporalSegment{
|
||||
Type: TemporalRelative,
|
||||
Direction: DirectionBackward,
|
||||
Count: tt.count,
|
||||
}
|
||||
|
||||
result, err := nav.Navigate(segment)
|
||||
|
||||
if tt.shouldError {
|
||||
if err == nil {
|
||||
t.Error("Expected error but got none")
|
||||
}
|
||||
if result.Success {
|
||||
t.Error("Result should indicate failure")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Error("Result should indicate success")
|
||||
}
|
||||
}
|
||||
|
||||
if nav.GetCurrentVersion() != tt.expectedPos {
|
||||
t.Errorf("Current version = %d, want %d", nav.GetCurrentVersion(), tt.expectedPos)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNavigateForward(t *testing.T) {
|
||||
nav := NewTemporalNavigator(10)
|
||||
nav.currentVersion = 5
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
count int
|
||||
shouldError bool
|
||||
expectedPos int
|
||||
}{
|
||||
{
|
||||
name: "valid forward navigation",
|
||||
count: 3,
|
||||
shouldError: false,
|
||||
expectedPos: 8,
|
||||
},
|
||||
{
|
||||
name: "forward to max version",
|
||||
count: 5,
|
||||
shouldError: false,
|
||||
expectedPos: 10,
|
||||
},
|
||||
{
|
||||
name: "forward beyond max version",
|
||||
count: 10,
|
||||
shouldError: true,
|
||||
expectedPos: 5, // Should stay at original position
|
||||
},
|
||||
{
|
||||
name: "negative count",
|
||||
count: -1,
|
||||
shouldError: true,
|
||||
expectedPos: 5, // Should stay at original position
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
nav.currentVersion = 5 // Reset position
|
||||
|
||||
segment := TemporalSegment{
|
||||
Type: TemporalRelative,
|
||||
Direction: DirectionForward,
|
||||
Count: tt.count,
|
||||
}
|
||||
|
||||
result, err := nav.Navigate(segment)
|
||||
|
||||
if tt.shouldError {
|
||||
if err == nil {
|
||||
t.Error("Expected error but got none")
|
||||
}
|
||||
if result.Success {
|
||||
t.Error("Result should indicate failure")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Error("Result should indicate success")
|
||||
}
|
||||
}
|
||||
|
||||
if nav.GetCurrentVersion() != tt.expectedPos {
|
||||
t.Errorf("Current version = %d, want %d", nav.GetCurrentVersion(), tt.expectedPos)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNavigationHistory(t *testing.T) {
|
||||
nav := NewTemporalNavigator(10)
|
||||
|
||||
// Perform several navigations
|
||||
segments := []TemporalSegment{
|
||||
{Type: TemporalSpecific, Count: 5},
|
||||
{Type: TemporalRelative, Direction: DirectionBackward, Count: 2},
|
||||
{Type: TemporalLatest},
|
||||
}
|
||||
|
||||
for _, segment := range segments {
|
||||
nav.Navigate(segment)
|
||||
}
|
||||
|
||||
history := nav.GetHistory()
|
||||
if len(history) != 3 {
|
||||
t.Errorf("History length = %d, want 3", len(history))
|
||||
}
|
||||
|
||||
// Check that all steps are recorded
|
||||
for i, step := range history {
|
||||
if step.Operation == "" {
|
||||
t.Errorf("Step %d should have operation recorded", i)
|
||||
}
|
||||
if step.Timestamp.IsZero() {
|
||||
t.Errorf("Step %d should have timestamp", i)
|
||||
}
|
||||
if !step.Success {
|
||||
t.Errorf("Step %d should be successful", i)
|
||||
}
|
||||
}
|
||||
|
||||
// Test clear history
|
||||
nav.ClearHistory()
|
||||
if len(nav.GetHistory()) != 0 {
|
||||
t.Error("History should be empty after ClearHistory()")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetMaxVersion(t *testing.T) {
|
||||
nav := NewTemporalNavigator(10)
|
||||
nav.currentVersion = 5
|
||||
|
||||
// Test increasing max version
|
||||
err := nav.SetMaxVersion(15)
|
||||
if err != nil {
|
||||
t.Errorf("SetMaxVersion(15) error = %v, want nil", err)
|
||||
}
|
||||
if nav.GetMaxVersion() != 15 {
|
||||
t.Errorf("Max version = %d, want 15", nav.GetMaxVersion())
|
||||
}
|
||||
if nav.GetCurrentVersion() != 5 {
|
||||
t.Errorf("Current version should remain at 5, got %d", nav.GetCurrentVersion())
|
||||
}
|
||||
|
||||
// Test decreasing max version below current
|
||||
err = nav.SetMaxVersion(3)
|
||||
if err != nil {
|
||||
t.Errorf("SetMaxVersion(3) error = %v, want nil", err)
|
||||
}
|
||||
if nav.GetMaxVersion() != 3 {
|
||||
t.Errorf("Max version = %d, want 3", nav.GetMaxVersion())
|
||||
}
|
||||
if nav.GetCurrentVersion() != 3 {
|
||||
t.Errorf("Current version should be adjusted to 3, got %d", nav.GetCurrentVersion())
|
||||
}
|
||||
|
||||
// Test negative max version
|
||||
err = nav.SetMaxVersion(-1)
|
||||
if err == nil {
|
||||
t.Error("SetMaxVersion(-1) should return error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionInfo(t *testing.T) {
|
||||
nav := NewTemporalNavigator(10)
|
||||
|
||||
info := VersionInfo{
|
||||
Version: 5,
|
||||
Created: time.Now(),
|
||||
Author: "test-author",
|
||||
Description: "test version",
|
||||
Tags: []string{"stable", "release"},
|
||||
}
|
||||
|
||||
// Set version info
|
||||
nav.SetVersionInfo(5, info)
|
||||
|
||||
// Retrieve version info
|
||||
retrievedInfo, exists := nav.GetVersionInfo(5)
|
||||
if !exists {
|
||||
t.Error("Version info should exist")
|
||||
}
|
||||
if retrievedInfo.Author != info.Author {
|
||||
t.Errorf("Author = %s, want %s", retrievedInfo.Author, info.Author)
|
||||
}
|
||||
|
||||
// Test non-existent version
|
||||
_, exists = nav.GetVersionInfo(99)
|
||||
if exists {
|
||||
t.Error("Version info should not exist for version 99")
|
||||
}
|
||||
|
||||
// Test GetAllVersions
|
||||
allVersions := nav.GetAllVersions()
|
||||
if len(allVersions) != 1 {
|
||||
t.Errorf("All versions count = %d, want 1", len(allVersions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanNavigate(t *testing.T) {
|
||||
nav := NewTemporalNavigator(10)
|
||||
nav.currentVersion = 5
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
direction string
|
||||
count int
|
||||
expected bool
|
||||
}{
|
||||
{"can go backward 3", "backward", 3, true},
|
||||
{"can go backward 5", "backward", 5, true},
|
||||
{"cannot go backward 6", "backward", 6, false},
|
||||
{"can go forward 3", "forward", 3, true},
|
||||
{"can go forward 5", "forward", 5, true},
|
||||
{"cannot go forward 6", "forward", 6, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var result bool
|
||||
if tt.direction == "backward" {
|
||||
result = nav.CanNavigateBackward(tt.count)
|
||||
} else {
|
||||
result = nav.CanNavigateForward(tt.count)
|
||||
}
|
||||
|
||||
if result != tt.expected {
|
||||
t.Errorf("Can navigate %s %d = %v, want %v", tt.direction, tt.count, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateTemporalSegment(t *testing.T) {
|
||||
nav := NewTemporalNavigator(10)
|
||||
nav.currentVersion = 5
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
segment TemporalSegment
|
||||
shouldError bool
|
||||
}{
|
||||
{
|
||||
name: "latest is valid",
|
||||
segment: TemporalSegment{Type: TemporalLatest},
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "any is valid",
|
||||
segment: TemporalSegment{Type: TemporalAny},
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "valid specific version",
|
||||
segment: TemporalSegment{Type: TemporalSpecific, Count: 7},
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "specific version out of range",
|
||||
segment: TemporalSegment{Type: TemporalSpecific, Count: 15},
|
||||
shouldError: true,
|
||||
},
|
||||
{
|
||||
name: "valid backward navigation",
|
||||
segment: TemporalSegment{Type: TemporalRelative, Direction: DirectionBackward, Count: 3},
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "backward navigation out of range",
|
||||
segment: TemporalSegment{Type: TemporalRelative, Direction: DirectionBackward, Count: 10},
|
||||
shouldError: true,
|
||||
},
|
||||
{
|
||||
name: "valid forward navigation",
|
||||
segment: TemporalSegment{Type: TemporalRelative, Direction: DirectionForward, Count: 3},
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "forward navigation out of range",
|
||||
segment: TemporalSegment{Type: TemporalRelative, Direction: DirectionForward, Count: 10},
|
||||
shouldError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := nav.ValidateTemporalSegment(tt.segment)
|
||||
|
||||
if tt.shouldError {
|
||||
if err == nil {
|
||||
t.Error("Expected error but got none")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNavigatorClone(t *testing.T) {
|
||||
nav := NewTemporalNavigator(10)
|
||||
nav.currentVersion = 5
|
||||
|
||||
// Add some version info and history
|
||||
nav.SetVersionInfo(5, VersionInfo{Author: "test"})
|
||||
nav.Navigate(TemporalSegment{Type: TemporalLatest})
|
||||
|
||||
cloned := nav.Clone()
|
||||
|
||||
// Test that basic properties are cloned
|
||||
if cloned.GetCurrentVersion() != nav.GetCurrentVersion() {
|
||||
t.Error("Current version should be cloned")
|
||||
}
|
||||
if cloned.GetMaxVersion() != nav.GetMaxVersion() {
|
||||
t.Error("Max version should be cloned")
|
||||
}
|
||||
|
||||
// Test that history is cloned
|
||||
originalHistory := nav.GetHistory()
|
||||
clonedHistory := cloned.GetHistory()
|
||||
if !reflect.DeepEqual(originalHistory, clonedHistory) {
|
||||
t.Error("History should be cloned")
|
||||
}
|
||||
|
||||
// Test that version info is cloned
|
||||
originalVersions := nav.GetAllVersions()
|
||||
clonedVersions := cloned.GetAllVersions()
|
||||
if !reflect.DeepEqual(originalVersions, clonedVersions) {
|
||||
t.Error("Version info should be cloned")
|
||||
}
|
||||
|
||||
// Test independence - modifying clone shouldn't affect original
|
||||
cloned.currentVersion = 0
|
||||
if nav.GetCurrentVersion() == cloned.GetCurrentVersion() {
|
||||
t.Error("Clone should be independent")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLastNavigation(t *testing.T) {
|
||||
nav := NewTemporalNavigator(10)
|
||||
|
||||
// Initially should return nil
|
||||
last := nav.GetLastNavigation()
|
||||
if last != nil {
|
||||
t.Error("GetLastNavigation() should return nil when no navigation has occurred")
|
||||
}
|
||||
|
||||
// After navigation should return the step
|
||||
segment := TemporalSegment{Type: TemporalSpecific, Count: 5}
|
||||
nav.Navigate(segment)
|
||||
|
||||
last = nav.GetLastNavigation()
|
||||
if last == nil {
|
||||
t.Error("GetLastNavigation() should return the last navigation step")
|
||||
}
|
||||
if last.Operation != segment.String() {
|
||||
t.Errorf("Operation = %s, want %s", last.Operation, segment.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark tests
|
||||
func BenchmarkNavigate(b *testing.B) {
|
||||
nav := NewTemporalNavigator(100)
|
||||
segment := TemporalSegment{Type: TemporalSpecific, Count: 50}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
nav.Navigate(segment)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkValidateTemporalSegment(b *testing.B) {
|
||||
nav := NewTemporalNavigator(100)
|
||||
nav.currentVersion = 50
|
||||
segment := TemporalSegment{Type: TemporalRelative, Direction: DirectionBackward, Count: 10}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
nav.ValidateTemporalSegment(segment)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user