Initial vibecoded proof of concept
This commit is contained in:
parent
74812459af
commit
461318a656
61 changed files with 13306 additions and 0 deletions
117
tests/contract/test_query_api.lua
Normal file
117
tests/contract/test_query_api.lua
Normal 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)
|
0
tests/integration/main_spec.lua
Normal file
0
tests/integration/main_spec.lua
Normal file
129
tests/integration/test_document_indexing.lua
Normal file
129
tests/integration/test_document_indexing.lua
Normal 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)
|
143
tests/integration/test_query_workflow.lua
Normal file
143
tests/integration/test_query_workflow.lua
Normal 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)
|
174
tests/integration/test_virtual_buffer.lua
Normal file
174
tests/integration/test_virtual_buffer.lua
Normal 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)
|
180
tests/performance/test_query_performance.lua
Normal file
180
tests/performance/test_query_performance.lua
Normal 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)
|
32
tests/spec/document_spec.lua
Normal file
32
tests/spec/document_spec.lua
Normal 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)
|
||||
|
0
tests/spec/query_spec.lua
Normal file
0
tests/spec/query_spec.lua
Normal file
23
tests/spec/schema_spec.lua
Normal file
23
tests/spec/schema_spec.lua
Normal 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)
|
324
tests/unit/utils/test_cache.lua
Normal file
324
tests/unit/utils/test_cache.lua
Normal 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)
|
277
tests/unit/utils/test_date.lua
Normal file
277
tests/unit/utils/test_date.lua
Normal 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)
|
247
tests/unit/utils/test_types.lua
Normal file
247
tests/unit/utils/test_types.lua
Normal 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)
|
282
tests/unit/utils/test_validation.lua
Normal file
282
tests/unit/utils/test_validation.lua
Normal 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)
|
Loading…
Add table
Add a link
Reference in a new issue