Files
2025-06-10 03:12:18 +00:00

266 lines
9.3 KiB
Ruby

# frozen_string_literal: true
RSpec.describe ActiveContext::Query do
describe 'class methods' do
describe '.all' do
it 'creates an all query' do
query = described_class.all
expect(query.type).to eq(:all)
expect(query.value).to be_nil
expect(query.children).to be_empty
end
end
describe '.filter' do
it 'creates a filter query with valid conditions' do
query = described_class.filter(project_id: 1)
expect(query.type).to eq(:filter)
expect(query.value).to eq(project_id: 1)
end
it 'raises an error for empty conditions' do
expect { described_class.filter }.to raise_error(ArgumentError, "Filter cannot be empty")
end
end
describe '.prefix' do
it 'creates a prefix query with valid conditions' do
query = described_class.prefix(traversal_ids: '9970-')
expect(query.type).to eq(:prefix)
expect(query.value).to eq(traversal_ids: '9970-')
end
it 'raises an error for empty conditions' do
expect { described_class.prefix }.to raise_error(ArgumentError, "Prefix cannot be empty")
end
end
describe '.or' do
it 'creates an OR query with multiple queries' do
query1 = described_class.filter(project_id: 1)
query2 = described_class.prefix(traversal_ids: 1)
or_query = described_class.or(query1, query2)
expect(or_query.type).to eq(:or)
expect(or_query.children).to contain_exactly(query1, query2)
end
it 'raises an error for empty queries' do
expect { described_class.or }.to raise_error(ArgumentError, "Or cannot be empty")
end
end
describe '.and' do
it 'creates an AND query with multiple queries' do
query1 = described_class.filter(project_id: 1)
query2 = described_class.filter(status: 'active')
and_query = described_class.and(query1, query2)
expect(and_query.type).to eq(:and)
expect(and_query.children).to contain_exactly(query1, query2)
end
it 'raises an error for empty queries' do
expect { described_class.and }.to raise_error(ArgumentError, "And cannot be empty")
end
end
end
describe 'instance methods' do
describe '#or' do
it 'creates a new query with OR logic' do
base_query = described_class.filter(project_id: 1)
additional_query1 = described_class.filter(status: 'active')
additional_query2 = described_class.prefix(traversal_ids: '9970-')
or_query = base_query.or(additional_query1, additional_query2)
expect(or_query.type).to eq(:and)
expect(or_query.children.size).to eq(2)
expect(or_query.children.first).to eq(base_query)
or_child = or_query.children.last
expect(or_child.type).to eq(:or)
expect(or_child.children).to contain_exactly(additional_query1, additional_query2)
end
it 'raises an error for empty queries' do
base_query = described_class.filter(project_id: 1)
expect { base_query.or }.to raise_error(ArgumentError, "Or cannot be empty")
end
end
describe '#and' do
it 'creates a new query with AND logic' do
base_query = described_class.filter(project_id: 1)
additional_query = described_class.filter(status: 'active')
and_query = base_query.and(additional_query)
expect(and_query.type).to eq(:and)
expect(and_query.children).to contain_exactly(base_query, additional_query)
end
it 'raises an error for empty queries' do
base_query = described_class.filter(project_id: 1)
expect { base_query.and }.to raise_error(ArgumentError, "And cannot be empty")
end
end
describe '#limit' do
it 'creates a limit query' do
base_query = described_class.filter(project_id: 1)
limited_query = base_query.limit(5)
expect(limited_query.type).to eq(:limit)
expect(limited_query.value).to eq(5)
expect(limited_query.children).to contain_exactly(base_query)
end
it 'raises an error for nil limit' do
base_query = described_class.filter(project_id: 1)
expect { base_query.limit(nil) }.to raise_error(ArgumentError, "Limit cannot be empty")
end
it 'raises an error for non-integer limit' do
base_query = described_class.filter(project_id: 1)
expect { base_query.limit('5') }.to raise_error(ArgumentError, /Limit must be a number/)
end
end
describe '#knn' do
it 'creates a KNN query with limit' do
base_query = described_class.filter(project_id: 1)
vector = [0.1, 0.2, 0.3]
knn_query = base_query.knn(target: 'similarity', vector: vector, k: 5)
expect(knn_query.type).to eq(:knn)
expect(knn_query.value).to eq(
target: 'similarity',
vector: vector,
k: 5
)
expect(knn_query.children).to contain_exactly(base_query)
end
it 'passes content when specified' do
content = 'something'
base_query = described_class.filter(project_id: 1)
knn_query = base_query.knn(content: content, k: 5)
expect(knn_query.type).to eq(:knn)
expect(knn_query.value).to eq(
content: content,
k: 5
)
expect(knn_query.children).to contain_exactly(base_query)
end
it 'raises an error for nil target and content' do
base_query = described_class.filter(project_id: 1)
vector = [0.1, 0.2, 0.3]
expect { base_query.knn(target: nil, vector: vector, k: 5) }
.to raise_error(ArgumentError, /:content must be provided OR both :target AND :vector must be provided/)
end
it 'raises an error for nil limit' do
base_query = described_class.filter(project_id: 1)
vector = [0.1, 0.2, 0.3]
expect { base_query.knn(target: 'similarity', vector: vector, k: nil) }
.to raise_error(ArgumentError, /K must be a positive number/)
end
it 'raises an error for non-array vector' do
base_query = described_class.filter(project_id: 1)
expect do
base_query.knn(target: 'similarity', vector: 'not an array', k: 5)
end.to raise_error(ArgumentError, "Vector must be an array")
end
it 'raises an error for non-positive limit' do
base_query = described_class.filter(project_id: 1)
vector = [0.1, 0.2, 0.3]
expect do
base_query.knn(target: 'similarity', vector: vector, k: 0)
end.to raise_error(ArgumentError, /K must be a positive number/)
expect do
base_query.knn(target: 'similarity', vector: vector, k: -1)
end.to raise_error(ArgumentError, /K must be a positive number/)
end
end
describe '#inspect_ast' do
it 'generates a readable AST representation for a simple all query' do
query = described_class.all
ast = query.inspect_ast
expect(ast).to eq('all')
end
it 'generates a readable AST representation for an all query with limit' do
query = described_class.all.limit(10)
ast = query.inspect_ast
expect(ast).to eq("limit(10)\n all")
end
it 'generates a readable AST representation for a simple filter query' do
query = described_class.filter(project_id: 1)
ast = query.inspect_ast
expect(ast).to eq('filter(project_id: 1)')
end
it 'generates a readable AST representation for a complex nested query' do
complex_query = described_class.filter(hello: :foo)
.or(
described_class.filter(project_id: 1),
described_class.prefix(traversal_ids: '9970-')
)
ast = complex_query.inspect_ast
expect(ast).to include('and')
expect(ast).to include('filter(hello: foo)')
expect(ast).to include('or')
expect(ast).to include('filter(project_id: 1)')
expect(ast).to include('prefix(traversal_ids: 9970-)')
end
it 'generates a readable AST representation for a KNN query with limit' do
base_query = described_class.filter(project_id: 1)
vector = [0.1, 0.2, 0.3]
knn_query = base_query.knn(target: 'similarity', vector: vector, k: 5)
ast = knn_query.inspect_ast
expect(ast).to eq("knn(target: similarity, vector: [0.1, 0.2, 0.3], k: 5)\n filter(project_id: 1)")
end
it 'generates a readable AST representation for a KNN query with content' do
base_query = described_class.filter(project_id: 1)
knn_query = base_query.knn(content: 'something', k: 5)
ast = knn_query.inspect_ast
expect(ast).to eq("knn(content: something, k: 5)\n filter(project_id: 1)")
end
it 'generates a readable AST representation for a KNN query without a base query' do
vector = [0.1, 0.2, 0.3]
knn_query = described_class.knn(target: 'similarity', vector: vector, k: 5)
ast = knn_query.inspect_ast
expect(ast).to eq('knn(target: similarity, vector: [0.1, 0.2, 0.3], k: 5)')
end
end
describe 'initialization' do
it 'raises an error for invalid query type' do
expect { described_class.new(type: :invalid) }.to raise_error(
ArgumentError,
/Invalid type: invalid\. Allowed types are: all, filter, prefix, limit, knn, and, or/
)
end
end
end
end