Initial vibecoded proof of concept

This commit is contained in:
Alex Selimov 2025-10-05 20:16:33 -04:00
parent 74812459af
commit 461318a656
Signed by: aselimov
GPG key ID: 3DDB9C3E023F1F31
61 changed files with 13306 additions and 0 deletions

View file

@ -0,0 +1,117 @@
-- Contract tests for Query API
local busted = require('busted')
describe("Query API Contract Tests", function()
local query_parser, query_executor, buffer_manager
before_each(function()
-- These modules don't exist yet - tests should fail
query_parser = require('notex.query.parser')
query_executor = require('notex.query.executor')
buffer_manager = require('notex.ui.buffer')
end)
describe("Query Parsing API", function()
it("should parse basic property filters", function()
local query_string = [[
```notex-query
status: "draft"
priority: "high"
```
]]
local result = query_parser.parse(query_string)
assert.are.equal("draft", result.filters.status)
assert.are.equal("high", result.filters.priority)
assert.is_not_nil(result.order_by)
end)
it("should parse WHERE conditions", function()
local query_string = [[
```notex-query
FROM status: "active"
WHERE priority > 5 AND created_at >= "2024-01-01"
```
]]
local result = query_parser.parse(query_string)
assert.is_not_nil(result.conditions)
assert.are.equal("AND", result.conditions.type)
assert.are.equal(2, #result.conditions.clauses)
end)
it("should handle parse errors gracefully", function()
local invalid_query = [[
```notex-query
WHERE INVALID SYNTAX HERE
```
]]
assert.has_error(function()
query_parser.parse(invalid_query)
end)
end)
end)
describe("Query Execution API", function()
it("should execute parsed queries and return results", function()
local parsed_query = {
filters = { status = "draft" },
conditions = nil,
order_by = { field = "created_at", direction = "DESC" },
limit = 50
}
local result = query_executor.execute(parsed_query)
assert.is_not_nil(result.documents)
assert.is_number(result.total_count)
assert.is_number(result.execution_time_ms)
assert.is_string(result.query_hash)
end)
it("should handle execution errors gracefully", function()
local invalid_query = {
filters = { nonexistent_property = "value" }
}
local result = query_executor.execute(invalid_query)
assert.is_not_nil(result.error)
assert.are.equal("execution_error", result.error.error_type)
end)
end)
describe("Virtual Buffer API", function()
it("should create query view buffers", function()
local query_results = {
documents = {
{
id = "doc123",
file_path = "/path/to/document.md",
properties = {
status = "draft",
priority = "high"
}
}
},
total_count = 1
}
local buffer_config = buffer_manager.create_query_view(query_results)
assert.is_not_nil(buffer_config.buffer_id)
assert.is_not_nil(buffer_config.window_id)
assert.is_table(buffer_config.lines)
assert.is_table(buffer_config.mappings)
end)
it("should update document properties", function()
local update_result = buffer_manager.update_document_property("doc123", "status", "review")
assert.is_true(update_result.success)
assert.are.equal("doc123", update_result.document_id)
assert.are.equal("status", update_result.property)
assert.are.equal("review", update_result.new_value)
end)
end)
end)

View file

View file

@ -0,0 +1,129 @@
-- Integration tests for document indexing workflow
local busted = require('busted')
describe("Document Indexing Workflow Integration", function()
local indexer
before_each(function()
-- These modules don't exist yet - tests should fail
indexer = require('notex.index')
end)
it("should index markdown files with YAML headers", function()
local test_files = {
"/tmp/test_doc1.md",
"/tmp/test_doc2.md"
}
-- Create test markdown files
local file1 = io.open(test_files[1], "w")
file1:write([[
---
title: "Test Document 1"
status: "draft"
priority: "high"
tags: ["test", "urgent"]
---
# Test Document 1
This is a test document with YAML header.
]])
file1:close()
local file2 = io.open(test_files[2], "w")
file2:write([[
---
title: "Test Document 2"
status: "review"
priority: "medium"
tags: ["test", "review"]
---
# Test Document 2
Another test document.
]])
file2:close()
-- Index the documents
local result = indexer.index_documents("/tmp")
assert.is_true(result.success)
assert.are.equal(2, result.indexed_count)
-- Clean up
os.remove(test_files[1])
os.remove(test_files[2])
end)
it("should handle documents with malformed YAML headers", function()
local malformed_file = "/tmp/malformed.md"
local file = io.open(malformed_file, "w")
file:write([[
---
title: "Malformed Document"
status: "draft"
invalid_yaml: [unclosed array
---
# Malformed Document
This has bad YAML.
]])
file1:close()
local result = indexer.index_documents("/tmp")
assert.is_true(result.success)
assert.are.equal(0, result.indexed_count)
assert.is_not_nil(result.errors)
assert.are.equal(1, #result.errors)
-- Clean up
os.remove(malformed_file)
end)
it("should incrementally update index when files change", function()
local test_file = "/tmp/incremental_test.md"
-- Create initial document
local file = io.open(test_file, "w")
file:write([[
---
title: "Incremental Test"
status: "draft"
---
# Incremental Test
Initial content.
]])
file:close()
-- Initial indexing
local result1 = indexer.index_documents("/tmp")
assert.is_true(result1.success)
assert.are.equal(1, result1.indexed_count)
-- Modify the file
vim.wait(100) -- Ensure different timestamp
local file2 = io.open(test_file, "w")
file2:write([[
---
title: "Incremental Test"
status: "review"
priority: "high"
---
# Incremental Test
Modified content.
]])
file2:close()
-- Incremental update
local result2 = indexer.update_index("/tmp")
assert.is_true(result2.success)
assert.are.equal(1, result2.updated_count)
-- Clean up
os.remove(test_file)
end)
end)

View file

@ -0,0 +1,143 @@
-- Integration tests for query workflow
local busted = require('busted')
describe("Query Workflow Integration", function()
local query_engine
before_each(function()
-- These modules don't exist yet - tests should fail
query_engine = require('notex.query')
end)
it("should execute end-to-end query workflow", function()
-- Setup test data
local test_documents = {
{
id = "doc1",
file_path = "/tmp/doc1.md",
properties = {
title = "Project Plan",
status = "draft",
priority = "high",
created_at = "2024-03-15T10:30:00Z"
}
},
{
id = "doc2",
file_path = "/tmp/doc2.md",
properties = {
title = "Meeting Notes",
status = "review",
priority = "medium",
created_at = "2024-03-14T15:20:00Z"
}
}
}
-- Initialize query engine with test data
query_engine.initialize(test_documents)
-- Execute query
local query_string = [[
```notex-query
status: "draft"
priority: "high"
ORDER BY created_at DESC
```
]]
local result = query_engine.execute_query(query_string)
-- Validate results
assert.is_not_nil(result.documents)
assert.are.equal(1, #result.documents)
assert.are.equal("Project Plan", result.documents[1].properties.title)
assert.are.equal("draft", result.documents[1].properties.status)
assert.is_true(result.execution_time_ms < 100) -- Performance requirement
end)
it("should handle complex queries with conditions", function()
local test_documents = {
{
id = "doc1",
properties = {
title = "Important Task",
status = "active",
priority = 5,
created_at = "2024-01-15T10:00:00Z",
tags = {"urgent", "project"}
}
},
{
id = "doc2",
properties = {
title = "Regular Task",
status = "active",
priority = 2,
created_at = "2024-02-01T14:30:00Z",
tags = {"routine"}
}
}
}
query_engine.initialize(test_documents)
local complex_query = [[
```notex-query
FROM status: "active"
WHERE priority > 3 AND tags INCLUDES "urgent"
ORDER BY created_at DESC
```
]]
local result = query_engine.execute_query(complex_query)
assert.are.equal(1, #result.documents)
assert.are.equal("Important Task", result.documents[1].properties.title)
end)
it("should handle queries that return no results", function()
local test_documents = {
{
id = "doc1",
properties = {
title = "Document 1",
status = "archived"
}
}
}
query_engine.initialize(test_documents)
local query = [[
```notex-query
status: "active"
```
]]
local result = query_engine.execute_query(query)
assert.are.equal(0, #result.documents)
assert.are.equal(0, result.total_count)
end)
it("should save and reuse queries", function()
local query_name = "My Active Tasks"
local query_definition = [[
```notex-query
status: "active"
priority: "high"
```
]]
-- Save query
local save_result = query_engine.save_query(query_name, query_definition)
assert.is_true(save_result.success)
-- List saved queries
local queries = query_engine.list_saved_queries()
assert.is_not_nil(queries[query_name])
-- Execute saved query
local result = query_engine.execute_saved_query(query_name)
assert.is_not_nil(result)
assert.is_number(result.execution_time_ms)
end)
end)

View file

@ -0,0 +1,174 @@
-- Integration tests for virtual buffer workflow
local busted = require('busted')
describe("Virtual Buffer Workflow Integration", function()
local ui_manager
before_each(function()
-- These modules don't exist yet - tests should fail
ui_manager = require('notex.ui')
end)
it("should create virtual buffer for query results", function()
local query_results = {
documents = {
{
id = "doc1",
file_path = "/tmp/document.md",
properties = {
title = "Test Document",
status = "draft",
priority = "high",
created_at = "2024-03-15T10:30:00Z"
}
}
},
total_count = 1,
execution_time_ms = 25
}
local buffer_result = ui_manager.show_query_results(query_results)
assert.is_not_nil(buffer_result.buffer_id)
assert.is_not_nil(buffer_result.window_id)
assert.is_table(buffer_result.lines)
assert.is_table(buffer_result.mappings)
-- Verify buffer content
local lines = buffer_result.lines
assert.is_true(#lines > 0)
-- Check for header line
local found_header = false
for _, line in ipairs(lines) do
if line:find("Results for:") then
found_header = true
break
end
end
assert.is_true(found_header, "Buffer should contain query results header")
end)
it("should handle keyboard interactions in virtual buffer", function()
local query_results = {
documents = {
{
id = "doc1",
file_path = "/tmp/document.md",
properties = {
title = "Test Document",
status = "draft"
}
}
},
total_count = 1
}
local buffer_result = ui_manager.show_query_results(query_results)
-- Test opening document with Enter key
local open_result = ui_manager.handle_keypress(buffer_result.buffer_id, "<CR>", 3)
assert.is_true(open_result.success)
assert.are.equal("/tmp/document.md", open_result.file_path)
-- Test editing with 'e' key
local edit_result = ui_manager.handle_keypress(buffer_result.buffer_id, "e", 3)
assert.is_true(edit_result.success)
assert.are.equal("doc1", edit_result.document_id)
-- Test closing with 'q' key
local close_result = ui_manager.handle_keypress(buffer_result.buffer_id, "q", 1)
assert.is_true(close_result.success)
end)
it("should update document properties through virtual buffer", function()
-- Create initial buffer
local query_results = {
documents = {
{
id = "doc1",
file_path = "/tmp/document.md",
properties = {
title = "Test Document",
status = "draft",
priority = "high"
}
}
},
total_count = 1
}
local buffer_result = ui_manager.show_query_results(query_results)
-- Simulate user editing status field
local update_result = ui_manager.update_property_in_buffer(
buffer_result.buffer_id,
3, -- line number
2, -- column number
"review" -- new value
)
assert.is_true(update_result.success)
assert.are.equal("doc1", update_result.document_id)
assert.are.equal("status", update_result.property)
assert.are.equal("review", update_result.new_value)
assert.are.equal("draft", update_result.old_value)
-- Verify the underlying file was updated
local updated_content = ui_manager.get_document_content("doc1")
assert.is_not_nil(updated_content:find('status: "review"'))
end)
it("should handle large query result sets efficiently", function()
-- Generate large test dataset
local large_results = { documents = {}, total_count = 1000, execution_time_ms = 45 }
for i = 1, 1000 do
table.insert(large_results.documents, {
id = "doc" .. i,
file_path = "/tmp/doc" .. i .. ".md",
properties = {
title = "Document " .. i,
status = i % 2 == 0 and "active" or "draft",
priority = math.random(1, 5)
}
})
end
local start_time = vim.loop.hrtime()
local buffer_result = ui_manager.show_query_results(large_results)
local end_time = vim.loop.hrtime()
local buffer_creation_time = (end_time - start_time) / 1e6 -- Convert to milliseconds
assert.is_not_nil(buffer_result.buffer_id)
assert.is_true(buffer_creation_time < 100, "Buffer creation should be under 100ms")
assert.is_true(#buffer_result.lines <= 100, "Should limit lines for performance")
end)
it("should gracefully handle buffer cleanup", function()
local query_results = {
documents = {
{
id = "doc1",
properties = { title = "Test Document" }
}
},
total_count = 1
}
local buffer_result = ui_manager.show_query_results(query_results)
-- Verify buffer exists
local buffer_exists = vim.api.nvim_buf_is_valid(buffer_result.buffer_id)
assert.is_true(buffer_exists)
-- Close buffer
local close_result = ui_manager.close_query_view(buffer_result.buffer_id)
assert.is_true(close_result.success)
-- Verify buffer no longer exists
buffer_exists = vim.api.nvim_buf_is_valid(buffer_result.buffer_id)
assert.is_false(buffer_exists)
end)
end)

View file

@ -0,0 +1,180 @@
-- Performance tests for query execution
local query_engine = require('notex.query')
describe("query performance", function()
local setup_test_data = function()
-- This would normally be handled by the test setup
-- For now, we'll assume test data exists
return true
end
before_each(function()
setup_test_data()
end)
describe("query execution time", function()
it("should execute simple queries under 100ms", function()
local start_time = vim.loop.hrtime()
local result = query_engine.execute_query('FROM documents LIMIT 10')
local end_time = vim.loop.hrtime()
local execution_time_ms = (end_time - start_time) / 1000000
assert.is_true(result.success, "Query should execute successfully")
assert.is_true(execution_time_ms < 100,
string.format("Query took %.2fms, expected < 100ms", execution_time_ms))
end)
it("should execute complex queries under 5 seconds", function()
local start_time = vim.loop.hrtime()
local result = query_engine.execute_query([[
FROM documents
WHERE status = "published"
ORDER BY created_at DESC
LIMIT 100
]])
local end_time = vim.loop.hrtime()
local execution_time_ms = (end_time - start_time) / 1000000
assert.is_true(result.success, "Query should execute successfully")
assert.is_true(execution_time_ms < 5000,
string.format("Complex query took %.2fms, expected < 5000ms", execution_time_ms))
end)
end)
describe("caching performance", function()
it("should improve performance with cached queries", function()
local cache = require('notex.utils.cache')
cache.init({lru = {enabled = true, max_size = 100}})
local query = 'FROM documents WHERE tags LIKE "test" ORDER BY updated_at DESC LIMIT 20'
-- First execution (cold cache)
local start_time = vim.loop.hrtime()
local result1 = query_engine.execute_query(query)
local cold_time = (vim.loop.hrtime() - start_time) / 1000000
-- Second execution (warm cache)
start_time = vim.loop.hrtime()
local result2 = query_engine.execute_query(query)
local warm_time = (vim.loop.hrtime() - start_time) / 1000000
assert.is_true(result1.success, "First query should succeed")
assert.is_true(result2.success, "Second query should succeed")
-- Warm cache should be faster (or at least not significantly slower)
local improvement_ratio = warm_time / cold_time
assert.is_true(improvement_ratio <= 1.5,
string.format("Cache didn't help: cold=%.2fms, warm=%.2fms, ratio=%.2f",
cold_time, warm_time, improvement_ratio))
cache.cleanup()
end)
end)
describe("concurrent query performance", function()
it("should handle multiple concurrent queries", function()
local queries = {
'FROM documents LIMIT 10',
'FROM documents WHERE status = "draft" LIMIT 10',
'FROM documents WHERE created_at > "2023-01-01" LIMIT 10',
'FROM documents ORDER BY updated_at DESC LIMIT 10'
}
local start_time = vim.loop.hrtime()
local results = {}
local errors = {}
-- Execute queries concurrently (simulated with immediate execution)
for i, query in ipairs(queries) do
local ok, result = pcall(query_engine.execute_query, query)
if ok then
results[i] = result
else
errors[i] = result
end
end
local total_time = (vim.loop.hrtime() - start_time) / 1000000
assert.equals(#queries, #results, "All queries should execute")
assert.equals(0, #errors, "No query errors should occur")
for _, result in ipairs(results) do
assert.is_true(result.success, "Each query should succeed")
end
-- Should complete in reasonable time
assert.is_true(total_time < 1000,
string.format("Concurrent queries took %.2fms, expected < 1000ms", total_time))
end)
end)
describe("large result set performance", function()
it("should handle large result sets efficiently", function()
local start_time = vim.loop.hrtime()
local result = query_engine.execute_query('FROM documents LIMIT 1000')
local end_time = vim.loop.hrtime()
local execution_time_ms = (end_time - start_time) / 1000000
assert.is_true(result.success, "Query should execute successfully")
if result.documents then
assert.is_true(#result.documents <= 1000, "Should not exceed limit")
end
-- Even large result sets should be reasonably fast
assert.is_true(execution_time_ms < 2000,
string.format("Large result set took %.2fms, expected < 2000ms", execution_time_ms))
end)
end)
describe("memory usage", function()
it("should not leak memory during repeated queries", function()
local initial_memory = collectgarbage("count")
local query = 'FROM documents LIMIT 10'
-- Execute many queries
for i = 1, 100 do
local result = query_engine.execute_query(query)
assert.is_true(result.success, "Query should succeed")
end
-- Force garbage collection
collectgarbage("collect")
collectgarbage("collect")
local final_memory = collectgarbage("count")
local memory_increase = final_memory - initial_memory
-- Memory increase should be minimal (< 1MB)
assert.is_true(memory_increase < 1000,
string.format("Memory increased by %.2fKB, expected < 1000KB", memory_increase))
end)
end)
describe("index performance", function()
it("should use indexes effectively", function()
-- Test query that should use index
local indexed_query = 'FROM documents WHERE id = "test-id"'
local start_time = vim.loop.hrtime()
local result = query_engine.execute_query(indexed_query)
local indexed_time = (vim.loop.hrtime() - start_time) / 1000000
assert.is_true(result.success, "Indexed query should succeed")
-- Should be very fast with index
assert.is_true(indexed_time < 50,
string.format("Indexed query took %.2fms, expected < 50ms", indexed_time))
end)
end)
end)

View file

@ -0,0 +1,32 @@
-- tests/spec/document_spec.lua
local document = require("notex.document")
describe("Document Parsing", function()
it("should parse a markdown file with YAML frontmatter", function()
local file_content = [[
---
type: person
name: John Doe
email: john.doe@example.com
---
# John Doe
This is a document about John Doe.
]]
local doc = document.parse(file_content)
print("doc.type: " .. doc.type)
for k, v in pairs(doc.properties) do
print("doc.properties[" .. k .. "]: " .. tostring(v))
end
print("doc.content: " .. doc.content)
assert.are.same("person", doc.type)
assert.are.same("John Doe", doc.properties.name)
assert.are.same("john.doe@example.com", doc.properties.email)
assert.are.same("# John Doe\n\nThis is a document about John Doe.\n", doc.content)
end)
end)

View file

View file

@ -0,0 +1,23 @@
-- tests/spec/schema_spec.lua
local schema = require("notex.schema")
describe("Schema Inference", function()
it("should infer schema from a document", function()
local doc = {
type = "person",
properties = {
name = "John Doe",
age = 30,
is_student = false,
},
}
local inferred_schema = schema.infer(doc)
assert.are.same({
name = "string",
age = "number",
is_student = "boolean",
}, inferred_schema)
end)
end)

View file

@ -0,0 +1,324 @@
-- Unit tests for caching utilities
local cache = require('notex.utils.cache')
describe("cache utilities", function()
before_each(function()
-- Reset cache before each test
cache.init({
memory = {enabled = true, max_size = 10},
lru = {enabled = true, max_size = 5},
timed = {enabled = true, default_ttl = 1}
})
end)
after_each(function()
cache.cleanup()
end)
describe("memory cache", function()
it("should store and retrieve values", function()
local ok = cache.memory_set("test_key", "test_value")
assert.is_true(ok)
local value = cache.memory_get("test_key")
assert.equals("test_value", value)
end)
it("should return nil for non-existent keys", function()
local value = cache.memory_get("non_existent")
assert.is_nil(value)
end)
it("should handle cache size limits", function()
-- Fill cache beyond max size
for i = 1, 15 do
cache.memory_set("key_" .. i, "value_" .. i)
end
-- Should still be able to set new values (eviction happens)
local ok = cache.memory_set("new_key", "new_value")
assert.is_true(ok)
-- Should still be able to get some values
local value = cache.memory_get("new_key")
assert.equals("new_value", value)
end)
end)
describe("LRU cache", function()
it("should store and retrieve values with LRU eviction", function()
cache.lru_set("key1", "value1")
cache.lru_set("key2", "value2")
cache.lru_set("key3", "value3")
-- Access key1 to make it most recently used
local value = cache.lru_get("key1")
assert.equals("value1", value)
-- Add more items to trigger eviction
cache.lru_set("key4", "value4")
cache.lru_set("key5", "value5")
cache.lru_set("key6", "value6") -- Should evict key2 (least recently used)
-- key2 should be evicted, key1 should still exist
assert.is_nil(cache.lru_get("key2"))
assert.equals("value1", cache.lru_get("key1"))
end)
it("should update access order on get", function()
cache.lru_set("key1", "value1")
cache.lru_set("key2", "value2")
-- Get key1 to make it most recently used
cache.lru_get("key1")
-- Fill cache to evict
cache.lru_set("key3", "value3")
cache.lru_set("key4", "value4")
cache.lru_set("key5", "value4")
cache.lru_set("key6", "value4") -- Should evict key2
assert.is_not_nil(cache.lru_get("key1")) -- Should still exist
assert.is_nil(cache.lru_get("key2")) -- Should be evicted
end)
end)
describe("timed cache", function()
it("should store values with TTL", function()
cache.timed_set("test_key", "test_value", 2) -- 2 second TTL
local value = cache.timed_get("test_key")
assert.equals("test_value", value)
end)
it("should expire values after TTL", function()
cache.timed_set("test_key", "test_value", 1) -- 1 second TTL
-- Should be available immediately
local value = cache.timed_get("test_key")
assert.equals("test_value", value)
-- Wait for expiration
vim.defer_fn(function()
value = cache.timed_get("test_key")
assert.is_nil(value)
end, 1100)
end)
it("should use default TTL when not specified", function()
cache.timed_set("test_key", "test_value")
local value = cache.timed_get("test_key")
assert.equals("test_value", value)
end)
end)
describe("generic cache operations", function()
it("should set and get with specified cache type", function()
local ok = cache.set("test_key", "test_value", "memory")
assert.is_true(ok)
local value = cache.get("test_key", "memory")
assert.equals("test_value", value)
end)
it("should default to memory cache", function()
local ok = cache.set("test_key", "test_value")
assert.is_true(ok)
local value = cache.get("test_key")
assert.equals("test_value", value)
end)
it("should handle unknown cache types", function()
local ok = cache.set("test_key", "test_value", "unknown")
assert.is_false(ok)
local value = cache.get("test_key", "unknown")
assert.is_nil(value)
end)
end)
describe("get_or_set", function()
it("should return cached value when exists", function()
cache.set("test_key", "cached_value")
local value = cache.get_or_set("test_key", function()
return "computed_value"
end)
assert.equals("cached_value", value)
end)
it("should compute and cache value when not exists", function()
local call_count = 0
local value = cache.get_or_set("test_key", function()
call_count = call_count + 1
return "computed_value"
end)
assert.equals("computed_value", value)
assert.equals(1, call_count)
-- Second call should use cached value
value = cache.get_or_set("test_key", function()
call_count = call_count + 1
return "new_value"
end)
assert.equals("computed_value", value)
assert.equals(1, call_count) -- Should not be called again
end)
it("should handle computation errors", function()
assert.has_error(function()
cache.get_or_set("test_key", function()
error("Computation failed")
end)
end)
end)
end)
describe("multi_get", function()
it("should try multiple cache types in order", function()
cache.set("test_key", "memory_value", "memory")
cache.set("test_key", "lru_value", "lru")
local value, cache_type = cache.multi_get("test_key", {"lru", "memory"})
assert.equals("lru_value", value)
assert.equals("lru", cache_type)
-- Should try memory if lru doesn't have it
cache.invalidate("test_key", "lru")
value, cache_type = cache.multi_get("test_key", {"lru", "memory"})
assert.equals("memory_value", value)
assert.equals("memory", cache_type)
end)
it("should return nil when not found in any cache", function()
local value = cache.multi_get("non_existent", {"memory", "lru", "timed"})
assert.is_nil(value)
end)
it("should use default cache types when not specified", function()
cache.set("test_key", "memory_value", "memory")
local value = cache.multi_get("test_key")
assert.equals("memory_value", value)
end)
end)
describe("invalidate", function()
it("should invalidate from specific cache type", function()
cache.set("test_key", "test_value", "memory")
cache.set("test_key", "test_value", "lru")
cache.invalidate("test_key", "memory")
assert.is_nil(cache.get("test_key", "memory"))
assert.equals("test_value", cache.get("test_key", "lru"))
end)
it("should invalidate from all caches when type not specified", function()
cache.set("test_key", "test_value", "memory")
cache.set("test_key", "test_value", "lru")
cache.set("test_key", "test_value", "timed")
cache.invalidate("test_key")
assert.is_nil(cache.get("test_key", "memory"))
assert.is_nil(cache.get("test_key", "lru"))
assert.is_nil(cache.get("test_key", "timed"))
end)
end)
describe("clear_all", function()
it("should clear all caches", function()
cache.set("key1", "value1", "memory")
cache.set("key2", "value2", "lru")
cache.set("key3", "value3", "timed")
cache.clear_all()
assert.is_nil(cache.get("key1", "memory"))
assert.is_nil(cache.get("key2", "lru"))
assert.is_nil(cache.get("key3", "timed"))
end)
it("should reset metrics", function()
-- Generate some metrics
cache.set("test", "value")
cache.get("test")
cache.get("non_existent")
local stats_before = cache.get_stats()
assert.is_true(stats_before.metrics.hits > 0)
assert.is_true(stats_before.metrics.misses > 0)
cache.clear_all()
local stats_after = cache.get_stats()
assert.equals(0, stats_after.metrics.hits)
assert.equals(0, stats_after.metrics.misses)
end)
end)
describe("get_stats", function()
it("should return comprehensive statistics", function()
-- Generate some activity
cache.set("test1", "value1")
cache.set("test2", "value2")
cache.get("test1")
cache.get("non_existent")
local stats = cache.get_stats()
assert.is_not_nil(stats.metrics)
assert.is_not_nil(stats.sizes)
assert.is_not_nil(stats.config)
assert.is_true(stats.metrics.sets >= 2)
assert.is_true(stats.metrics.hits >= 1)
assert.is_true(stats.metrics.misses >= 1)
assert.is_not_nil(stats.metrics.hit_ratio)
end)
it("should calculate hit ratio correctly", function()
-- Generate known metrics
cache.set("test", "value")
cache.get("test") -- hit
cache.get("test") -- hit
cache.get("non_existent") -- miss
local stats = cache.get_stats()
-- Should be 2 hits out of 3 total requests = ~0.67
assert.is_true(stats.metrics.hit_ratio > 0.6)
assert.is_true(stats.metrics.hit_ratio < 0.7)
end)
end)
describe("configuration", function()
it("should initialize with custom configuration", function()
cache.init({
memory = {enabled = false},
lru = {enabled = true, max_size = 20},
timed = {enabled = true, default_ttl = 10}
})
-- Memory cache should be disabled
local ok = cache.memory_set("test", "value")
assert.is_false(ok)
-- LRU cache should work with new size
ok = cache.lru_set("test", "value")
assert.is_true(ok)
-- Timed cache should work with new TTL
cache.timed_set("test2", "value")
local value = cache.timed_get("test2")
assert.equals("value", value)
end)
end)
end)

View file

@ -0,0 +1,277 @@
-- Unit tests for date parsing and formatting utilities
local date_utils = require('notex.utils.date')
describe("date utilities", function()
describe("parse_date", function()
it("should parse ISO 8601 dates", function()
local timestamp = date_utils.parse_date("2023-12-25")
assert.is_not_nil(timestamp)
local formatted = os.date("%Y-%m-%d", timestamp)
assert.equals("2023-12-25", formatted)
end)
it("should parse ISO 8601 datetimes", function()
local timestamp = date_utils.parse_date("2023-12-25T10:30:00")
assert.is_not_nil(timestamp)
local formatted = os.date("%Y-%m-%d %H:%M:%S", timestamp)
assert.equals("2023-12-25 10:30:00", formatted)
end)
it("should parse ISO 8601 with timezone", function()
local timestamp = date_utils.parse_date("2023-12-25T10:30:00+02:00")
assert.is_not_nil(timestamp)
-- Should be converted to UTC
local utc_formatted = os.date("%Y-%m-%d %H:%M:%S", timestamp)
assert.equals("2023-12-25 08:30:00", utc_formatted)
end)
it("should parse relative dates", function()
-- Test 1 day ago
local one_day_ago = os.time() - 86400
local timestamp = date_utils.parse_date("1d")
assert.is_true(math.abs(timestamp - one_day_ago) < 60) -- Within 1 minute
-- Test 1 hour ago
local one_hour_ago = os.time() - 3600
timestamp = date_utils.parse_date("1h")
assert.is_true(math.abs(timestamp - one_hour_ago) < 60) -- Within 1 minute
end)
it("should parse natural dates", function()
local timestamp = date_utils.parse_date("2023-12-25")
assert.is_not_nil(timestamp)
local formatted = os.date("%Y-%m-%d", timestamp)
assert.equals("2023-12-25", formatted)
end)
it("should return nil for invalid dates", function()
local timestamp = date_utils.parse_date("invalid date")
assert.is_nil(timestamp)
timestamp = date_utils.parse_date("")
assert.is_nil(timestamp)
timestamp = date_utils.parse_date(nil)
assert.is_nil(timestamp)
end)
it("should parse common date formats", function()
-- MM/DD/YYYY
local timestamp = date_utils.parse_date("12/25/2023")
assert.is_not_nil(timestamp)
-- MM-DD-YYYY
timestamp = date_utils.parse_date("12-25-2023")
assert.is_not_nil(timestamp)
end)
end)
describe("format_date", function()
it("should format timestamp to string", function()
local timestamp = os.time({year = 2023, month = 12, day = 25, hour = 10, min = 30, sec = 0})
local formatted = date_utils.format_date(timestamp, "%Y-%m-%d %H:%M:%S")
assert.equals("2023-12-25 10:30:00", formatted)
end)
it("should use default format", function()
local timestamp = os.time({year = 2023, month = 12, day = 25})
local formatted = date_utils.format_date(timestamp)
assert.equals("2023-12-25", formatted)
end)
it("should handle nil timestamp", function()
local formatted = date_utils.format_date(nil)
assert.equals("", formatted)
end)
end)
describe("get_relative_time", function()
it("should return 'just now' for recent times", function()
local current_time = os.time()
local relative = date_utils.get_relative_time(current_time)
assert.equals("just now", relative)
end)
it("should return minutes ago", function()
local timestamp = os.time() - 120 -- 2 minutes ago
local relative = date_utils.get_relative_time(timestamp)
assert.equals("2 minutes ago", relative)
end)
it("should return hours ago", function()
local timestamp = os.time() - 7200 -- 2 hours ago
local relative = date_utils.get_relative_time(timestamp)
assert.equals("2 hours ago", relative)
end)
it("should return days ago", function()
local timestamp = os.time() - 172800 -- 2 days ago
local relative = date_utils.get_relative_time(timestamp)
assert.equals("2 days ago", relative)
end)
it("should return months ago", function()
local timestamp = os.time() - (60 * 86400) -- ~2 months ago
local relative = date_utils.get_relative_time(timestamp)
assert.matches("months ago", relative)
end)
it("should return years ago", function()
local timestamp = os.time() - (365 * 86400) -- ~1 year ago
local relative = date_utils.get_relative_time(timestamp)
assert.matches("year ago", relative)
end)
it("should handle singular forms", function()
local timestamp = os.time() - 60 -- 1 minute ago
local relative = date_utils.get_relative_time(timestamp)
assert.equals("1 minute ago", relative)
timestamp = os.time() - 3600 -- 1 hour ago
relative = date_utils.get_relative_time(timestamp)
assert.equals("1 hour ago", relative)
end)
end)
describe("get_month_number", function()
it("should convert month names to numbers", function()
assert.equals(1, date_utils.get_month_number("January"))
assert.equals(12, date_utils.get_month_number("December"))
assert.equals(6, date_utils.get_month_number("June"))
end)
it("should handle short month names", function()
assert.equals(1, date_utils.get_month_number("Jan"))
assert.equals(12, date_utils.get_month_number("Dec"))
assert.equals(6, date_utils.get_month_number("Jun"))
end)
it("should be case insensitive", function()
assert.equals(1, date_utils.get_month_number("JANUARY"))
assert.equals(1, date_utils.get_month_number("january"))
assert.equals(1, date_utils.get_month_number("jan"))
end)
it("should return nil for invalid month names", function()
assert.is_nil(date_utils.get_month_number("NotAMonth"))
assert.is_nil(date_utils.get_month_number(""))
end)
end)
describe("get_month_name", function()
it("should convert month numbers to names", function()
assert.equals("January", date_utils.get_month_name(1))
assert.equals("December", date_utils.get_month_name(12))
assert.equals("June", date_utils.get_month_name(6))
end)
it("should return short names when requested", function()
assert.equals("Jan", date_utils.get_month_name(1, true))
assert.equals("Dec", date_utils.get_month_name(12, true))
assert.equals("Jun", date_utils.get_month_name(6, true))
end)
it("should return nil for invalid month numbers", function()
assert.is_nil(date_utils.get_month_name(0))
assert.is_nil(date_utils.get_month_name(13))
assert.is_nil(date_utils.get_month_name(-1))
end)
end)
describe("is_valid_date", function()
it("should validate correct dates", function()
assert.is_true(date_utils.is_valid_date("2023-12-25"))
assert.is_true(date_utils.is_valid_date("2023-12-25T10:30:00"))
assert.is_true(date_utils.is_valid_date("1d"))
end)
it("should reject invalid dates", function()
assert.is_false(date_utils.is_valid_date("invalid"))
assert.is_false(date_utils.is_valid_date(""))
assert.is_false(date_utils.is_valid_date(nil))
end)
end)
describe("add_time", function()
it("should add time to timestamp", function()
local timestamp = os.time({year = 2023, month = 12, day = 25})
local new_timestamp = date_utils.add_time(timestamp, 1, "days")
local expected = os.time({year = 2023, month = 12, day = 26})
assert.equals(expected, new_timestamp)
end)
it("should add different time units", function()
local timestamp = os.time({year = 2023, month = 12, day = 25, hour = 10})
-- Add hours
local new_timestamp = date_utils.add_time(timestamp, 2, "hours")
local expected = os.time({year = 2023, month = 12, day = 25, hour = 12})
assert.equals(expected, new_timestamp)
-- Add minutes
new_timestamp = date_utils.add_time(timestamp, 30, "minutes")
expected = os.time({year = 2023, month = 12, day = 25, hour = 10, min = 30})
assert.equals(expected, new_timestamp)
end)
end)
describe("get_date_range", function()
it("should calculate date range", function()
local range = date_utils.get_date_range("2023-12-25", "2023-12-27")
assert.is_not_nil(range)
assert.equals(2, range.duration_days)
assert.equals("2023-12-25", range.start_formatted)
assert.equals("2023-12-27", range.end_formatted)
end)
it("should return nil for invalid dates", function()
local range = date_utils.get_date_range("invalid", "2023-12-27")
assert.is_nil(range)
end)
end)
describe("get_week_bounds", function()
it("should get week start and end", function()
local week_bounds = date_utils.get_week_bounds()
assert.is_not_nil(week_bounds.start_timestamp)
assert.is_not_nil(week_bounds.end_timestamp)
assert.is_not_nil(week_bounds.start_formatted)
assert.is_not_nil(week_bounds.end_formatted)
-- Should be 7 days apart
local duration = week_bounds.end_timestamp - week_bounds.start_timestamp
assert.equals(6 * 86400, duration) -- 6 days in seconds
end)
end)
describe("get_month_bounds", function()
it("should get month start and end", function()
local month_bounds = date_utils.get_month_bounds(os.time({year = 2023, month = 12, day = 15}))
assert.is_not_nil(month_bounds.start_timestamp)
assert.is_not_nil(month_bounds.end_timestamp)
assert.equals("2023-12-01", month_bounds.start_formatted)
assert.equals("2023-12-31", month_bounds.end_formatted)
end)
end)
describe("get_timezones", function()
it("should return list of timezones", function()
local timezones = date_utils.get_timezones()
assert.is_table(timezones)
assert.is_true(#timezones > 0)
assert.is_true(vim.tbl_contains(timezones, "UTC"))
assert.is_true(vim.tbl_contains(timezones, "America/New_York"))
end)
end)
end)

View file

@ -0,0 +1,247 @@
-- Unit tests for type detection and conversion utilities
local types = require('notex.utils.types')
describe("type utilities", function()
describe("detect_type", function()
it("should detect boolean true values", function()
local detected_type, converted_value = types.detect_type("true")
assert.equals("boolean", detected_type)
assert.is_true(converted_value)
detected_type, converted_value = types.detect_type("yes")
assert.equals("boolean", detected_type)
assert.is_true(converted_value)
detected_type, converted_value = types.detect_type("1")
assert.equals("boolean", detected_type)
assert.is_true(converted_value)
end)
it("should detect boolean false values", function()
local detected_type, converted_value = types.detect_type("false")
assert.equals("boolean", detected_type)
assert.is_false(converted_value)
detected_type, converted_value = types.detect_type("no")
assert.equals("boolean", detected_type)
assert.is_false(converted_value)
detected_type, converted_value = types.detect_type("0")
assert.equals("boolean", detected_type)
assert.is_false(converted_value)
end)
it("should detect ISO 8601 dates", function()
local detected_type = types.detect_type("2023-12-25")
assert.equals("date", detected_type)
detected_type = types.detect_type("2023-12-25T10:30:00")
assert.equals("date", detected_type)
end)
it("should detect URLs", function()
local detected_type = types.detect_type("https://example.com")
assert.equals("url", detected_type)
detected_type = types.detect_type("http://test.org/path")
assert.equals("url", detected_type)
end)
it("should detect email addresses", function()
local detected_type = types.detect_type("user@example.com")
assert.equals("email", detected_type)
detected_type = types.detect_type("test.email+tag@domain.co.uk")
assert.equals("email", detected_type)
end)
it("should detect numbers", function()
local detected_type = types.detect_type("42")
assert.equals("number", detected_type)
detected_type = types.detect_type("-17")
assert.equals("number", detected_type)
detected_type = types.detect_type("3.14159")
assert.equals("number", detected_type)
end)
it("should detect JSON arrays", function()
local detected_type = types.detect_type('[1, 2, 3]')
assert.equals("array", detected_type)
detected_type = types.detect_type('["a", "b", "c"]')
assert.equals("array", detected_type)
end)
it("should detect JSON objects", function()
local detected_type = types.detect_type('{"key": "value"}')
assert.equals("object", detected_type)
detected_type = types.detect_type('{"a": 1, "b": 2}')
assert.equals("object", detected_type)
end)
it("should detect strings by default", function()
local detected_type = types.detect_type("plain text")
assert.equals("string", detected_type)
detected_type = types.detect_type("not a special pattern")
assert.equals("string", detected_type)
end)
it("should detect nil values", function()
local detected_type = types.detect_type(nil)
assert.equals("nil", detected_type)
end)
end)
describe("convert_to_type", function()
it("should convert to boolean", function()
assert.is_true(types.convert_to_type("true", "boolean"))
assert.is_false(types.convert_to_type("false", "boolean"))
assert.is_true(types.convert_to_type("yes", "boolean"))
assert.is_false(types.convert_to_type("no", "boolean"))
end)
it("should convert to number", function()
assert.equals(42, types.convert_to_type("42", "number"))
assert.equals(-17.5, types.convert_to_type("-17.5", "number"))
assert.equals(0, types.convert_to_type("invalid", "number"))
end)
it("should convert to string", function()
assert.equals("hello", types.convert_to_type("hello", "string"))
assert.equals("42", types.convert_to_type(42, "string"))
assert.equals("true", types.convert_to_type(true, "string"))
end)
it("should convert to array", function()
local array = types.convert_to_type('[1, 2, 3]', "array")
assert.is_table(array)
assert.equals(1, array[1])
assert.equals(2, array[2])
assert.equals(3, array[3])
-- Comma-separated values
array = types.convert_to_type("a,b,c", "array")
assert.is_table(array)
assert.equals("a", array[1])
assert.equals("b", array[2])
assert.equals("c", array[3])
end)
it("should convert to object", function()
local obj = types.convert_to_type('{"key": "value"}', "object")
assert.is_table(obj)
assert.equals("value", obj.key)
-- Key=value pairs
obj = types.convert_to_type("a=1,b=2", "object")
assert.is_table(obj)
assert.equals("1", obj.a)
assert.equals("2", obj.b)
end)
end)
describe("compare_types", function()
it("should compare types correctly", function()
local result = types.compare_types("hello", 42)
assert.equals("string", result.type1)
assert.equals("number", result.type2)
assert.is_false(result.same_type)
assert.is_true(result.compatible) -- strings can represent numbers
result = types.compare_types(42, 17)
assert.equals("number", result.type1)
assert.equals("number", result.type2)
assert.is_true(result.same_type)
assert.is_true(result.compatible)
end)
end)
describe("are_types_compatible", function()
it("should check type compatibility", function()
assert.is_true(types.are_types_compatible("string", "string"))
assert.is_true(types.are_types_compatible("number", "number"))
assert.is_true(types.are_types_compatible("string", "number")) -- string can convert to number
assert.is_true(types.are_types_compatible("number", "string")) -- string can represent number
assert.is_false(types.are_types_compatible("boolean", "table"))
end)
end)
describe("cast_value", function()
it("should cast values with validation", function()
assert.equals(42, types.cast_value("42", "number"))
assert.equals("hello", types.cast_value("hello", "string"))
-- Invalid cast returns original in non-strict mode
assert.equals("invalid", types.cast_value("invalid", "number"))
end)
it("should error in strict mode", function()
assert.has_error(function()
types.cast_value("invalid", "number", true)
end)
end)
end)
describe("infer_schema", function()
it("should infer schema from values", function()
local values = {"hello", "world", "test"}
local schema = types.infer_schema(values)
assert.equals("string", schema.detected_type)
assert.equals(1.0, schema.confidence)
assert.equals(3, schema.sample_size)
assert.equals(5, schema.constraints.max_length)
assert.equals(4, schema.constraints.min_length)
end)
it("should handle mixed types", function()
local values = {"hello", 42, true}
local schema = types.infer_schema(values)
-- Should pick the most common type
assert.is_not_nil(schema.detected_type)
assert.is_true(schema.confidence < 1.0)
end)
end)
describe("get_possible_conversions", function()
it("should get all possible conversions", function()
local conversions = types.get_possible_conversions("42")
-- Should include number, boolean, string, and possibly date conversions
local found_types = {}
for _, conversion in ipairs(conversions) do
found_types[conversion.type] = true
end
assert.is_true(found_types.number)
assert.is_true(found_types.string)
assert.is_true(found_types.boolean)
end)
end)
describe("validate_conversion", function()
it("should validate type conversion", function()
assert.is_true(types.validate_conversion("42", "number"))
assert.is_true(types.validate_conversion("true", "boolean"))
assert.is_false(types.validate_conversion("invalid", "number"))
end)
end)
describe("get_type_info", function()
it("should get comprehensive type information", function()
local info = types.get_type_info("42")
assert.equals("number", info.detected_type)
assert.equals("42", info.original_value)
assert.equals(42, info.converted_value)
assert.is_table(info.possible_conversions)
end)
end)
end)

View file

@ -0,0 +1,282 @@
-- Unit tests for validation utilities
local validation = require('notex.utils.validation')
describe("validation utilities", function()
describe("validate_value", function()
it("should validate string values", function()
local schema = {type = "string", required = true}
local valid, error = validation.validate_value("hello", schema)
assert.is_true(valid)
assert.equals("Validation passed", error)
end)
it("should reject required string when nil", function()
local schema = {type = "string", required = true}
local valid, error = validation.validate_value(nil, schema)
assert.is_false(valid)
assert.equals("Value is required", error)
end)
it("should validate string length constraints", function()
local schema = {type = "string", min_length = 5, max_length = 10}
-- Too short
local valid, error = validation.validate_value("hi", schema)
assert.is_false(valid)
assert.matches("too short", error)
-- Too long
valid, error = validation.validate_value("this is too long", schema)
assert.is_false(valid)
assert.matches("too long", error)
-- Just right
valid, error = validation.validate_value("perfect", schema)
assert.is_true(valid)
end)
it("should validate number values", function()
local schema = {type = "number", min_value = 1, max_value = 10}
-- Valid number
local valid, error = validation.validate_value(5, schema)
assert.is_true(valid)
-- Too small
valid, error = validation.validate_value(0, schema)
assert.is_false(valid)
assert.matches("too small", error)
-- Too large
valid, error = validation.validate_value(11, schema)
assert.is_false(valid)
assert.matches("too large", error)
end)
it("should validate boolean values", function()
local schema = {type = "boolean"}
-- Valid booleans
local valid, error = validation.validate_value(true, schema)
assert.is_true(valid)
valid, error = validation.validate_value(false, schema)
assert.is_true(valid)
-- String conversion
valid, error = validation.validate_value("true", schema)
assert.is_true(valid)
valid, error = validation.validate_value("false", schema)
assert.is_true(valid)
end)
it("should validate array values", function()
local schema = {type = "array", min_items = 2, max_items = 5}
-- Valid array
local valid, error = validation.validate_value({1, 2, 3}, schema)
assert.is_true(valid)
-- Too few items
valid, error = validation.validate_value({1}, schema)
assert.is_false(valid)
assert.matches("too short", error)
-- Too many items
valid, error = validation.validate_value({1, 2, 3, 4, 5, 6}, schema)
assert.is_false(valid)
assert.matches("too long", error)
end)
it("should validate object values", function()
local schema = {
type = "object",
required_fields = {"name"},
field_types = {
name = {type = "string"},
age = {type = "number"}
}
}
-- Valid object
local valid, error = validation.validate_value({name = "John", age = 30}, schema)
assert.is_true(valid)
-- Missing required field
valid, error = validation.validate_value({age = 30}, schema)
assert.is_false(valid)
assert.matches("Missing required field", error)
-- Invalid field type
valid, error = validation.validate_value({name = 123, age = 30}, schema)
assert.is_false(valid)
assert.matches("Field 'name' invalid", error)
end)
end)
describe("validate_document_properties", function()
it("should validate document properties against schema", function()
local schema_definition = {
title = {type = "string", required = true},
status = {type = "string", enum = {"draft", "published"}},
priority = {type = "number", min_value = 1, max_value = 5}
}
local properties = {
title = "Test Document",
status = "draft",
priority = 3
}
local valid, result = validation.validate_document_properties(properties, schema_definition)
assert.is_true(valid)
assert.equals(0, #result.errors)
end)
it("should return errors for invalid properties", function()
local schema_definition = {
title = {type = "string", required = true},
status = {type = "string", enum = {"draft", "published"}}
}
local properties = {
status = "invalid_status" -- Missing required title, invalid status
}
local valid, result = validation.validate_document_properties(properties, schema_definition)
assert.is_false(valid)
assert.is_true(#result.errors > 0)
end)
it("should include warnings for unknown properties", function()
local schema_definition = {
title = {type = "string", required = true}
}
local properties = {
title = "Test Document",
unknown_property = "value"
}
local valid, result = validation.validate_document_properties(properties, schema_definition)
assert.is_true(valid)
assert.is_true(#result.warnings > 0)
end)
end)
describe("validate_query_params", function()
it("should validate query parameters", function()
local allowed_params = {
limit = {type = "number", min_value = 1, max_value = 100},
sort = {type = "string", enum = {"asc", "desc"}},
filter = {type = "string", required = false}
}
local params = {
limit = 10,
sort = "asc"
}
local valid, errors = validation.validate_query_params(params, allowed_params)
assert.is_true(valid)
assert.equals(0, #errors)
end)
it("should reject unknown parameters", function()
local allowed_params = {
limit = {type = "number"}
}
local params = {
limit = 10,
unknown = "value"
}
local valid, errors = validation.validate_query_params(params, allowed_params)
assert.is_false(valid)
assert.is_true(#errors > 0)
end)
end)
describe("sanitize_input", function()
it("should remove dangerous characters", function()
local input = '<script>alert("xss")</script>'
local sanitized = validation.sanitize_input(input)
assert.equals('scriptalertxss/script', sanitized)
end)
it("should limit length", function()
local input = string.rep("a", 100)
local sanitized = validation.sanitize_input(input, {max_length = 10})
assert.equals(10, #sanitized)
end)
it("should trim whitespace", function()
local input = " hello world "
local sanitized = validation.sanitize_input(input)
assert.equals("hello world", sanitized)
end)
end)
describe("validate_file_path", function()
it("should validate safe file paths", function()
local valid, error = validation.validate_file_path("/home/user/document.md")
assert.is_true(valid)
end)
it("should reject paths with invalid characters", function()
local valid, error = validation.validate_file_path('file<name>.md')
assert.is_false(valid)
assert.matches("Invalid characters", error)
end)
it("should reject directory traversal", function()
local valid, error = validation.validate_file_path("../../../etc/passwd")
assert.is_false(valid)
assert.matches("Directory traversal", error)
end)
it("should reject empty paths", function()
local valid, error = validation.validate_file_path("")
assert.is_false(valid)
assert.equals("Empty file path", error)
end)
end)
describe("create_schema", function()
it("should create validation schema", function()
local schema = validation.create_schema("test_field", {
type = "string",
required = true,
min_length = 5
})
assert.equals("test_field", schema.field_name)
assert.equals("string", schema.type)
assert.is_true(schema.required)
assert.equals(5, schema.min_length)
end)
end)
describe("create_validation_summary", function()
it("should create validation summary", function()
local results = {
{valid = true},
{valid = false, errors = {"Error 1"}},
{valid = false, errors = {"Error 2", "Error 3"}, warnings = {"Warning 1"}},
{valid = true, warnings = {"Warning 2"}}
}
local summary = validation.create_validation_summary(results)
assert.equals(4, summary.total)
assert.equals(2, summary.valid)
assert.equals(2, summary.invalid)
assert.equals(3, #summary.errors)
assert.equals(2, #summary.warnings)
end)
end)
end)