Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
duration_functions_test.go9.74 kB
// Tests for duration and temporal arithmetic in NornicDB Cypher implementation. package cypher import ( "context" "strings" "testing" "github.com/orneryd/nornicdb/pkg/storage" ) func TestParseDuration(t *testing.T) { tests := []struct { name string input string expected *CypherDuration }{ { name: "simple days", input: "P5D", expected: &CypherDuration{ Days: 5, }, }, { name: "years months days", input: "P1Y2M3D", expected: &CypherDuration{ Years: 1, Months: 2, Days: 3, }, }, { name: "time components", input: "PT4H30M15S", expected: &CypherDuration{ Hours: 4, Minutes: 30, Seconds: 15, }, }, { name: "full duration", input: "P1Y2M3DT4H5M6S", expected: &CypherDuration{ Years: 1, Months: 2, Days: 3, Hours: 4, Minutes: 5, Seconds: 6, }, }, { name: "lowercase", input: "p1y2m3dt4h5m6s", expected: &CypherDuration{ Years: 1, Months: 2, Days: 3, Hours: 4, Minutes: 5, Seconds: 6, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := parseDuration(tt.input) if got == nil { t.Fatalf("parseDuration(%q) returned nil", tt.input) } if got.Years != tt.expected.Years || got.Months != tt.expected.Months || got.Days != tt.expected.Days || got.Hours != tt.expected.Hours || got.Minutes != tt.expected.Minutes || got.Seconds != tt.expected.Seconds { t.Errorf("parseDuration(%q) = %+v, want %+v", tt.input, got, tt.expected) } }) } } func TestDurationString(t *testing.T) { tests := []struct { name string duration *CypherDuration expected string }{ { name: "days only", duration: &CypherDuration{Days: 5}, expected: "P5D", }, { name: "full duration", duration: &CypherDuration{Years: 1, Months: 2, Days: 3, Hours: 4, Minutes: 5, Seconds: 6}, expected: "P1Y2M3DT4H5M6S", }, { name: "time only", duration: &CypherDuration{Hours: 2, Minutes: 30}, expected: "PT2H30M", }, { name: "empty duration", duration: &CypherDuration{}, expected: "PT0S", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := tt.duration.String() if got != tt.expected { t.Errorf("String() = %q, want %q", got, tt.expected) } }) } } func TestDurationFunction(t *testing.T) { engine := storage.NewMemoryEngine() defer engine.Close() executor := NewStorageExecutor(engine) ctx := context.Background() t.Run("duration parses ISO string", func(t *testing.T) { result, err := executor.Execute(ctx, "RETURN duration('P5D') AS d", nil) if err != nil { t.Fatalf("Query failed: %v", err) } got, ok := result.Rows[0][0].(*CypherDuration) if !ok { t.Fatalf("Expected CypherDuration, got %T", result.Rows[0][0]) } if got.Days != 5 { t.Errorf("Expected 5 days, got %d", got.Days) } }) t.Run("duration with time components", func(t *testing.T) { result, err := executor.Execute(ctx, "RETURN duration('PT2H30M') AS d", nil) if err != nil { t.Fatalf("Query failed: %v", err) } got, ok := result.Rows[0][0].(*CypherDuration) if !ok { t.Fatalf("Expected CypherDuration, got %T", result.Rows[0][0]) } if got.Hours != 2 || got.Minutes != 30 { t.Errorf("Expected 2h30m, got %dh%dm", got.Hours, got.Minutes) } }) } func TestDurationBetween(t *testing.T) { engine := storage.NewMemoryEngine() defer engine.Close() executor := NewStorageExecutor(engine) ctx := context.Background() t.Run("duration between dates", func(t *testing.T) { result, err := executor.Execute(ctx, "RETURN duration.between('2025-01-01', '2025-01-06') AS d", nil) if err != nil { t.Fatalf("Query failed: %v", err) } got, ok := result.Rows[0][0].(*CypherDuration) if !ok { t.Fatalf("Expected CypherDuration, got %T", result.Rows[0][0]) } if got.Days != 5 { t.Errorf("Expected 5 days, got %d", got.Days) } }) t.Run("duration between datetimes", func(t *testing.T) { result, err := executor.Execute(ctx, "RETURN duration.between('2025-01-01T10:00:00', '2025-01-01T12:30:00') AS d", nil) if err != nil { t.Fatalf("Query failed: %v", err) } got, ok := result.Rows[0][0].(*CypherDuration) if !ok { t.Fatalf("Expected CypherDuration, got %T", result.Rows[0][0]) } if got.Hours != 2 || got.Minutes != 30 { t.Errorf("Expected 2h30m, got %dh%dm", got.Hours, got.Minutes) } }) } func TestDurationInDays(t *testing.T) { engine := storage.NewMemoryEngine() defer engine.Close() executor := NewStorageExecutor(engine) ctx := context.Background() result, err := executor.Execute(ctx, "RETURN duration.inDays(duration('P10D')) AS d", nil) if err != nil { t.Fatalf("Query failed: %v", err) } got, ok := result.Rows[0][0].(float64) if !ok { t.Fatalf("Expected float64, got %T", result.Rows[0][0]) } if got != 10.0 { t.Errorf("Expected 10 days, got %f", got) } } func TestDurationInSeconds(t *testing.T) { engine := storage.NewMemoryEngine() defer engine.Close() executor := NewStorageExecutor(engine) ctx := context.Background() result, err := executor.Execute(ctx, "RETURN duration.inSeconds(duration('PT1H')) AS s", nil) if err != nil { t.Fatalf("Query failed: %v", err) } got, ok := result.Rows[0][0].(float64) if !ok { t.Fatalf("Expected float64, got %T", result.Rows[0][0]) } if got != 3600.0 { t.Errorf("Expected 3600 seconds, got %f", got) } } func TestDurationTotalDays(t *testing.T) { d := &CypherDuration{Days: 5, Hours: 12} got := d.TotalDays() expected := 5.5 if got != expected { t.Errorf("TotalDays() = %f, want %f", got, expected) } } func TestDurationTotalSeconds(t *testing.T) { d := &CypherDuration{Hours: 1, Minutes: 30} got := d.TotalSeconds() expected := 5400.0 // 1.5 hours = 5400 seconds if got != expected { t.Errorf("TotalSeconds() = %f, want %f", got, expected) } } func TestDurationFromMap(t *testing.T) { m := map[string]interface{}{ "days": 5, "hours": 3, "minutes": 30, } got := durationFromMap(m) if got.Days != 5 || got.Hours != 3 || got.Minutes != 30 { t.Errorf("durationFromMap() = %+v, want {Days: 5, Hours: 3, Minutes: 30}", got) } } func TestAddDurationToDate(t *testing.T) { dur := &CypherDuration{Days: 5} result := addDurationToDate("2025-01-01", dur) if !strings.HasPrefix(result, "2025-01-06") { t.Errorf("addDurationToDate() = %s, want 2025-01-06*", result) } } func TestSubtractDurationFromDate(t *testing.T) { dur := &CypherDuration{Days: 5} result := subtractDurationFromDate("2025-01-10", dur) if !strings.HasPrefix(result, "2025-01-05") { t.Errorf("subtractDurationFromDate() = %s, want 2025-01-05*", result) } } func TestParseDateTime(t *testing.T) { tests := []struct { name string input string year int month int day int }{ { name: "date only", input: "2025-11-27", year: 2025, month: 11, day: 27, }, { name: "datetime", input: "2025-11-27T10:30:00", year: 2025, month: 11, day: 27, }, { name: "RFC3339", input: "2025-11-27T10:30:00Z", year: 2025, month: 11, day: 27, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := parseDateTime(tt.input) if got.IsZero() { t.Fatalf("parseDateTime(%q) returned zero time", tt.input) } if got.Year() != tt.year || int(got.Month()) != tt.month || got.Day() != tt.day { t.Errorf("parseDateTime(%q) = %v, want %d-%02d-%02d", tt.input, got, tt.year, tt.month, tt.day) } }) } } // Test date arithmetic via Cypher queries func TestDateArithmeticQueries(t *testing.T) { engine := storage.NewMemoryEngine() defer engine.Close() executor := NewStorageExecutor(engine) ctx := context.Background() t.Run("date + duration via query", func(t *testing.T) { result, err := executor.Execute(ctx, "RETURN date('2025-01-01') + duration('P5D') AS newDate", nil) if err != nil { t.Fatalf("Query failed: %v", err) } got := result.Rows[0][0].(string) if !strings.HasPrefix(got, "2025-01-06") { t.Errorf("Expected 2025-01-06*, got %s", got) } }) t.Run("date - duration via query", func(t *testing.T) { result, err := executor.Execute(ctx, "RETURN date('2025-01-10') - duration('P3D') AS newDate", nil) if err != nil { t.Fatalf("Query failed: %v", err) } got := result.Rows[0][0].(string) if !strings.HasPrefix(got, "2025-01-07") { t.Errorf("Expected 2025-01-07*, got %s", got) } }) t.Run("date - date returns duration", func(t *testing.T) { result, err := executor.Execute(ctx, "RETURN date('2025-01-10') - date('2025-01-01') AS diff", nil) if err != nil { t.Fatalf("Query failed: %v", err) } got, ok := result.Rows[0][0].(*CypherDuration) if !ok { t.Fatalf("Expected CypherDuration, got %T: %v", result.Rows[0][0], result.Rows[0][0]) } if got.Days != 9 { t.Errorf("Expected 9 days, got %d", got.Days) } }) t.Run("datetime + duration with hours", func(t *testing.T) { result, err := executor.Execute(ctx, "RETURN datetime('2025-01-01T10:00:00') + duration('PT3H') AS newDt", nil) if err != nil { t.Fatalf("Query failed: %v", err) } got := result.Rows[0][0].(string) if !strings.Contains(got, "13:00:00") { t.Errorf("Expected datetime with 13:00:00, got %s", got) } }) t.Run("duration + date (commutative)", func(t *testing.T) { result, err := executor.Execute(ctx, "RETURN duration('P7D') + date('2025-01-01') AS newDate", nil) if err != nil { t.Fatalf("Query failed: %v", err) } got := result.Rows[0][0].(string) if !strings.HasPrefix(got, "2025-01-08") { t.Errorf("Expected 2025-01-08*, got %s", got) } }) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server