mirror of
https://github.com/lukaszraczylo/claude-mnemonic.git
synced 2026-06-05 23:03:55 +00:00
5335a8a7a6
* Make things 'betterer' across the board * fix: reorganize struct fields and config parameters for consistency - [x] Reorder Config struct fields alphabetically and by related functionality - [x] Reorganize Observation model fields with archival fields grouped together - [x] Reorder ObservationStore fields to group related members - [x] Reorder Store struct fields with health check caching grouped - [x] Reorganize HealthInfo and PoolMetrics struct field order - [x] Reorder maintenance Service struct fields logically - [x] Reorganize MCP server handler parameter structs alphabetically - [x] Reorder pattern detector candidate tracking fields - [x] Reorganize search Manager struct fields by functionality - [x] Reorder vector Client struct fields with mutex protections grouped - [x] Reorganize handler request/response struct fields - [x] Update handlers_test.go to expect wrapped response format - [x] Reorder middleware TokenAuth and rate limiter fields - [x] Reorganize Service struct fields with grouped functionality - [x] Fix RateLimiter field ordering for clarity - [x] Reorder CircuitBreaker metrics fields * fix(security): improve JSON output safety and path traversal protection - [x] Replace unsafe JSON string formatting with proper json.Marshal in export handler - [x] Remove escapeJSONString helper function in favor of standard JSON marshaling - [x] Add safeResolvePath function to validate paths and prevent directory traversal - [x] Apply path traversal validation in captureFileMtimes operations - [x] Cap result slice capacity in getRecentSearchQueries to prevent DoS via excessive allocation * fix(sdk): improve path traversal protection and allocation safety - [x] Enhance safeResolvePath with stricter validation using filepath.Rel - [x] Reject paths containing ".." after cleaning to prevent traversal - [x] Validate absolute paths are within cwd when cwd is specified - [x] Apply safeResolvePath validation to GetFileContent for consistency - [x] Add comprehensive test coverage for path traversal protection - [x] Fix allocation safety in getRecentSearchQueries by using constant capacity * feat(dashboard): add graph stats and vector metrics endpoints - [x] Add handleGraphStats endpoint for knowledge graph visualization - [x] Add handleVectorMetrics endpoint for vector database dashboard - [x] Improve update check error handling with JSON response - [x] Register new API routes for graph and vector metrics - [x] Migrate Font Awesome to npm package from CDN - [x] Fix observations API response type handling - [x] Update package version to v0.10.5-15-g385d05a * fixup! feat(dashboard): add graph stats and vector metrics endpoints * test: add comprehensive test coverage across multiple packages - [x] Add 298 tests for Python chunker functionality - [x] Add 213 tests for chunking types and constants - [x] Add 398 tests for TypeScript/JavaScript chunker - [x] Add 954 tests for MCP server handlers and validation - [x] Add 563 tests for pattern detector and analysis - [x] Add 1149 tests for vector client cache and operations - [x] Add 663 tests for SDK processor, circuit breaker, and deduplication - [x] Add 731 tests for session manager lifecycle and concurrency - [x] Add 331 tests for similarity clustering and term extraction * fix(pattern): add nil check and fmt import for GetPatternInsight - [x] Add `fmt` import for error formatting - [x] Add nil check for pattern before using it - [x] Remove duplicate comment line
299 lines
6.9 KiB
Go
299 lines
6.9 KiB
Go
package python
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/lukaszraczylo/claude-mnemonic/internal/chunking"
|
|
)
|
|
|
|
// =============================================================================
|
|
// TEST HELPERS
|
|
// =============================================================================
|
|
|
|
func createTempPythonFile(t *testing.T, content string) string {
|
|
t.Helper()
|
|
|
|
tmpDir := t.TempDir()
|
|
filePath := filepath.Join(tmpDir, "test.py")
|
|
|
|
err := os.WriteFile(filePath, []byte(content), 0600)
|
|
require.NoError(t, err)
|
|
|
|
return filePath
|
|
}
|
|
|
|
// =============================================================================
|
|
// TESTS FOR Chunker
|
|
// =============================================================================
|
|
|
|
func TestNewChunker(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
opts := chunking.DefaultChunkOptions()
|
|
c := NewChunker(opts)
|
|
|
|
assert.NotNil(t, c)
|
|
assert.NotNil(t, c.parser)
|
|
}
|
|
|
|
func TestChunker_Language(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewChunker(chunking.DefaultChunkOptions())
|
|
|
|
assert.Equal(t, chunking.LanguagePython, c.Language())
|
|
}
|
|
|
|
func TestChunker_SupportedExtensions(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewChunker(chunking.DefaultChunkOptions())
|
|
exts := c.SupportedExtensions()
|
|
|
|
assert.Contains(t, exts, ".py")
|
|
}
|
|
|
|
func TestChunker_Chunk_SimpleFunction(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
code := `def greet(name):
|
|
"""Greets a person by name."""
|
|
return f"Hello, {name}!"
|
|
`
|
|
|
|
filePath := createTempPythonFile(t, code)
|
|
c := NewChunker(chunking.DefaultChunkOptions())
|
|
|
|
chunks, err := c.Chunk(context.Background(), filePath)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, chunks)
|
|
|
|
// Should find the greet function
|
|
var foundGreet bool
|
|
for _, chunk := range chunks {
|
|
if chunk.Name == "greet" {
|
|
foundGreet = true
|
|
assert.Equal(t, chunking.ChunkTypeFunction, chunk.Type)
|
|
assert.Equal(t, chunking.LanguagePython, chunk.Language)
|
|
assert.Contains(t, chunk.Content, "def greet")
|
|
}
|
|
}
|
|
assert.True(t, foundGreet, "Should find 'greet' function")
|
|
}
|
|
|
|
func TestChunker_Chunk_ClassWithMethods(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
code := `class Calculator:
|
|
"""A simple calculator class."""
|
|
|
|
def add(self, a, b):
|
|
"""Adds two numbers."""
|
|
return a + b
|
|
|
|
def multiply(self, a, b):
|
|
"""Multiplies two numbers."""
|
|
return a * b
|
|
`
|
|
|
|
filePath := createTempPythonFile(t, code)
|
|
c := NewChunker(chunking.DefaultChunkOptions())
|
|
|
|
chunks, err := c.Chunk(context.Background(), filePath)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, chunks)
|
|
|
|
// Should find the Calculator class and its methods
|
|
var foundClass, foundAdd, foundMultiply bool
|
|
for _, chunk := range chunks {
|
|
switch chunk.Name {
|
|
case "Calculator":
|
|
foundClass = true
|
|
assert.Equal(t, chunking.ChunkTypeClass, chunk.Type)
|
|
case "add":
|
|
foundAdd = true
|
|
assert.Equal(t, chunking.ChunkTypeMethod, chunk.Type)
|
|
assert.Equal(t, "Calculator", chunk.ParentName)
|
|
case "multiply":
|
|
foundMultiply = true
|
|
assert.Equal(t, chunking.ChunkTypeMethod, chunk.Type)
|
|
assert.Equal(t, "Calculator", chunk.ParentName)
|
|
}
|
|
}
|
|
|
|
assert.True(t, foundClass, "Should find 'Calculator' class")
|
|
assert.True(t, foundAdd, "Should find 'add' method")
|
|
assert.True(t, foundMultiply, "Should find 'multiply' method")
|
|
}
|
|
|
|
func TestChunker_Chunk_MultipleFunctions(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
code := `def first_function():
|
|
pass
|
|
|
|
def second_function(x, y):
|
|
return x + y
|
|
|
|
def third_function():
|
|
"""Has a docstring."""
|
|
return 42
|
|
`
|
|
|
|
filePath := createTempPythonFile(t, code)
|
|
c := NewChunker(chunking.DefaultChunkOptions())
|
|
|
|
chunks, err := c.Chunk(context.Background(), filePath)
|
|
require.NoError(t, err)
|
|
|
|
// Should find all three functions
|
|
functionNames := make(map[string]bool)
|
|
for _, chunk := range chunks {
|
|
if chunk.Type == chunking.ChunkTypeFunction {
|
|
functionNames[chunk.Name] = true
|
|
}
|
|
}
|
|
|
|
assert.True(t, functionNames["first_function"])
|
|
assert.True(t, functionNames["second_function"])
|
|
assert.True(t, functionNames["third_function"])
|
|
}
|
|
|
|
func TestChunker_Chunk_FileNotFound(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := NewChunker(chunking.DefaultChunkOptions())
|
|
|
|
_, err := c.Chunk(context.Background(), "/nonexistent/path/file.py")
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "read file")
|
|
}
|
|
|
|
func TestChunker_Chunk_EmptyFile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
filePath := createTempPythonFile(t, "")
|
|
c := NewChunker(chunking.DefaultChunkOptions())
|
|
|
|
chunks, err := c.Chunk(context.Background(), filePath)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, chunks)
|
|
}
|
|
|
|
func TestChunker_Chunk_OnlyComments(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
code := `# This is a comment
|
|
# Another comment
|
|
"""
|
|
This is a module docstring
|
|
"""
|
|
`
|
|
|
|
filePath := createTempPythonFile(t, code)
|
|
c := NewChunker(chunking.DefaultChunkOptions())
|
|
|
|
chunks, err := c.Chunk(context.Background(), filePath)
|
|
require.NoError(t, err)
|
|
// Comments and docstrings without code should not produce chunks
|
|
assert.Empty(t, chunks)
|
|
}
|
|
|
|
func TestChunker_Chunk_NestedClass(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
code := `class Outer:
|
|
class Inner:
|
|
def inner_method(self):
|
|
pass
|
|
|
|
def outer_method(self):
|
|
pass
|
|
`
|
|
|
|
filePath := createTempPythonFile(t, code)
|
|
c := NewChunker(chunking.DefaultChunkOptions())
|
|
|
|
chunks, err := c.Chunk(context.Background(), filePath)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, chunks)
|
|
|
|
// Should find the Outer class at minimum
|
|
var foundOuter bool
|
|
for _, chunk := range chunks {
|
|
if chunk.Name == "Outer" {
|
|
foundOuter = true
|
|
}
|
|
}
|
|
assert.True(t, foundOuter, "Should find 'Outer' class")
|
|
}
|
|
|
|
func TestChunker_Chunk_Decorators(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
code := `@staticmethod
|
|
def static_func():
|
|
pass
|
|
|
|
@classmethod
|
|
def class_func(cls):
|
|
pass
|
|
|
|
@property
|
|
def my_property(self):
|
|
return self._value
|
|
`
|
|
|
|
filePath := createTempPythonFile(t, code)
|
|
c := NewChunker(chunking.DefaultChunkOptions())
|
|
|
|
chunks, err := c.Chunk(context.Background(), filePath)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, chunks)
|
|
|
|
// Should find decorated functions
|
|
functionNames := make(map[string]bool)
|
|
for _, chunk := range chunks {
|
|
functionNames[chunk.Name] = true
|
|
}
|
|
|
|
assert.True(t, functionNames["static_func"])
|
|
assert.True(t, functionNames["class_func"])
|
|
assert.True(t, functionNames["my_property"])
|
|
}
|
|
|
|
func TestChunker_Chunk_AsyncFunction(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
code := `async def fetch_data(url):
|
|
"""Fetches data from URL asynchronously."""
|
|
pass
|
|
|
|
async def process_items(items):
|
|
for item in items:
|
|
await process(item)
|
|
`
|
|
|
|
filePath := createTempPythonFile(t, code)
|
|
c := NewChunker(chunking.DefaultChunkOptions())
|
|
|
|
chunks, err := c.Chunk(context.Background(), filePath)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, chunks)
|
|
|
|
// Should find async functions
|
|
functionNames := make(map[string]bool)
|
|
for _, chunk := range chunks {
|
|
functionNames[chunk.Name] = true
|
|
}
|
|
|
|
assert.True(t, functionNames["fetch_data"])
|
|
assert.True(t, functionNames["process_items"])
|
|
}
|