# 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