mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
test: add additional search test utilities
This commit is contained in:
@@ -0,0 +1,374 @@
|
||||
/**
|
||||
* Exhaustive Search Test Suite
|
||||
*
|
||||
* This test suite verifies ALL possible search patterns, combinations, and edge cases.
|
||||
* It uses both mock data and real-world data structures to ensure accuracy.
|
||||
*/
|
||||
|
||||
const { getSortedAndFilteredData } = require('./networkUtils');
|
||||
|
||||
// Create a more extensive mock data set with varied properties
|
||||
const generateExhaustiveTestData = () => {
|
||||
// Base test data similar to the real application
|
||||
const guests = [
|
||||
// PRIMARY GUESTS
|
||||
{
|
||||
id: '101',
|
||||
name: 'web-server', // Plain name
|
||||
type: 'qemu',
|
||||
status: 'running',
|
||||
node: 'pve-prod-01',
|
||||
shared: true,
|
||||
primaryNode: 'pve-prod-01',
|
||||
tags: ['web', 'production']
|
||||
},
|
||||
{
|
||||
id: '102',
|
||||
name: 'database-primary', // Has "primary" in name
|
||||
type: 'qemu',
|
||||
status: 'running',
|
||||
node: 'pve-prod-01',
|
||||
shared: true,
|
||||
primaryNode: 'pve-prod-01',
|
||||
tags: ['db', 'primary', 'production']
|
||||
},
|
||||
{
|
||||
id: '103',
|
||||
name: 'sprint-server', // Has "pri" as substring
|
||||
type: 'qemu',
|
||||
status: 'paused',
|
||||
node: 'pve-prod-01',
|
||||
shared: true,
|
||||
primaryNode: 'pve-prod-01',
|
||||
tags: ['sprint', 'production']
|
||||
},
|
||||
|
||||
// SECONDARY GUESTS
|
||||
{
|
||||
id: '201',
|
||||
name: 'web-secondary',
|
||||
type: 'qemu',
|
||||
status: 'running',
|
||||
node: 'pve-prod-02',
|
||||
shared: true,
|
||||
primaryNode: 'pve-prod-01',
|
||||
tags: ['web', 'secondary']
|
||||
},
|
||||
{
|
||||
id: '202',
|
||||
name: 'sec-database', // Has "sec" at start of name
|
||||
type: 'lxc',
|
||||
status: 'stopped',
|
||||
node: 'pve-prod-02',
|
||||
shared: true,
|
||||
primaryNode: 'pve-prod-01',
|
||||
tags: ['db', 'backup']
|
||||
},
|
||||
{
|
||||
id: '203',
|
||||
name: 'prism-backup', // Has "pri" as substring
|
||||
type: 'lxc',
|
||||
status: 'running',
|
||||
node: 'pve-prod-02',
|
||||
shared: true,
|
||||
primaryNode: 'pve-prod-01',
|
||||
tags: ['backup']
|
||||
},
|
||||
|
||||
// NON-SHARED GUESTS
|
||||
{
|
||||
id: '301',
|
||||
name: 'standalone-pri-app', // Has "pri" in name but is NOT primary
|
||||
type: 'qemu',
|
||||
status: 'running',
|
||||
node: 'pve-dev-01',
|
||||
shared: false,
|
||||
tags: ['dev', 'private']
|
||||
},
|
||||
{
|
||||
id: '302',
|
||||
name: 'security-container', // Has "sec" as substring but is NOT secondary
|
||||
type: 'lxc',
|
||||
status: 'stopped',
|
||||
node: 'pve-dev-01',
|
||||
shared: false,
|
||||
tags: ['security', 'dev']
|
||||
},
|
||||
|
||||
// EDGE CASES
|
||||
{
|
||||
id: '401',
|
||||
name: 'p', // Single-letter name matching a role search
|
||||
type: 'qemu',
|
||||
status: 'running',
|
||||
node: 'pve-edge-01',
|
||||
shared: true,
|
||||
primaryNode: 'pve-edge-01',
|
||||
tags: ['test']
|
||||
},
|
||||
{
|
||||
id: '402',
|
||||
name: 's', // Single-letter name matching a role search
|
||||
type: 'qemu',
|
||||
status: 'running',
|
||||
node: 'pve-edge-01',
|
||||
shared: true,
|
||||
primaryNode: 'pve-prod-01',
|
||||
tags: ['test']
|
||||
}
|
||||
];
|
||||
|
||||
// Add optional metrics data
|
||||
const metricsData = {
|
||||
cpu: {},
|
||||
memory: {},
|
||||
disk: {},
|
||||
network: {}
|
||||
};
|
||||
|
||||
guests.forEach(guest => {
|
||||
const id = guest.id;
|
||||
metricsData.cpu[id] = { usage: Math.random() * 100 };
|
||||
metricsData.memory[id] = { usagePercent: Math.random() * 100 };
|
||||
metricsData.disk[id] = { usagePercent: Math.random() * 100 };
|
||||
metricsData.network[id] = { inRate: Math.random() * 10000000, outRate: Math.random() * 10000000 };
|
||||
});
|
||||
|
||||
// Node data for name resolution
|
||||
const nodeData = [
|
||||
{ id: 'pve-prod-01', name: 'prod-cluster-1' },
|
||||
{ id: 'pve-prod-02', name: 'prod-cluster-2' },
|
||||
{ id: 'pve-dev-01', name: 'dev-cluster' },
|
||||
{ id: 'pve-edge-01', name: 'edge-node' }
|
||||
];
|
||||
|
||||
return { guests, metricsData, nodeData };
|
||||
};
|
||||
|
||||
// Function to run all exhaustive tests
|
||||
export function runExhaustiveTests() {
|
||||
console.log('===== EXHAUSTIVE SEARCH TEST SUITE =====');
|
||||
console.log('Testing all possible search patterns and edge cases');
|
||||
|
||||
const { guests, metricsData, nodeData } = generateExhaustiveTestData();
|
||||
|
||||
// Collection of all test cases
|
||||
const testCases = [
|
||||
// 1. ROLE BASED SEARCHES - STANDALONE TERMS
|
||||
// Each of these tests a specific role search term
|
||||
{ category: 'ROLE STANDALONE', term: 'role', description: 'All shared guests',
|
||||
expectIdsContaining: ['101', '102', '103', '201', '202', '203', '401', '402'] },
|
||||
|
||||
{ category: 'ROLE STANDALONE', term: 'shared', description: 'Alternative for all shared guests',
|
||||
expectIdsContaining: ['101', '102', '103', '201', '202', '203', '401', '402'] },
|
||||
|
||||
{ category: 'ROLE STANDALONE', term: 'primary', description: 'Finds primary guests',
|
||||
expectIdsContaining: ['101', '102', '103', '401'] },
|
||||
|
||||
{ category: 'ROLE STANDALONE', term: 'pri', description: 'Short form of primary',
|
||||
expectIdsContaining: ['101', '102', '103', '401'] },
|
||||
|
||||
{ category: 'ROLE STANDALONE', term: 'p', description: 'Shortest form of primary',
|
||||
expectIdsContaining: ['101', '102', '103', '401'] },
|
||||
|
||||
{ category: 'ROLE STANDALONE', term: 'secondary', description: 'Finds secondary guests',
|
||||
expectIdsContaining: ['201', '202', '203', '402'] },
|
||||
|
||||
{ category: 'ROLE STANDALONE', term: 'sec', description: 'Short form of secondary',
|
||||
expectIdsContaining: ['201', '202', '203', '402'] },
|
||||
|
||||
{ category: 'ROLE STANDALONE', term: 's', description: 'Shortest form of secondary',
|
||||
expectIdsContaining: ['201', '202', '203', '402'] },
|
||||
|
||||
// 2. ROLE COLUMN SEARCHES
|
||||
{ category: 'ROLE COLUMN', term: 'role:primary', description: 'Column search for primary',
|
||||
expectIdsContaining: ['101', '102', '103', '401'] },
|
||||
|
||||
{ category: 'ROLE COLUMN', term: 'role:pri', description: 'Column search with short primary',
|
||||
expectIdsContaining: ['101', '102', '103', '401'] },
|
||||
|
||||
{ category: 'ROLE COLUMN', term: 'role:p', description: 'Column search with shortest primary',
|
||||
expectIdsContaining: ['101', '102', '103', '401'] },
|
||||
|
||||
{ category: 'ROLE COLUMN', term: 'role:secondary', description: 'Column search for secondary',
|
||||
expectIdsContaining: ['201', '202', '203', '402'] },
|
||||
|
||||
{ category: 'ROLE COLUMN', term: 'role:sec', description: 'Column search with short secondary',
|
||||
expectIdsContaining: ['201', '202', '203', '402'] },
|
||||
|
||||
{ category: 'ROLE COLUMN', term: 'role:s', description: 'Column search with shortest secondary',
|
||||
expectIdsContaining: ['201', '202', '203', '402'] },
|
||||
|
||||
{ category: 'ROLE COLUMN', term: 'role:none', description: 'Column search for non-shared',
|
||||
expectIdsContaining: ['301', '302'] },
|
||||
|
||||
{ category: 'ROLE COLUMN', term: 'role:-', description: 'Column search for non-shared (dash)',
|
||||
expectIdsContaining: ['301', '302'] },
|
||||
|
||||
// 3. TEXT SEARCHES WITH ROLE COMPONENTS
|
||||
{ category: 'TEXT MATCH', term: 'primary-', description: 'Has primary in name but with a suffix',
|
||||
expectIdsContaining: ['102'] },
|
||||
|
||||
{ category: 'TEXT MATCH', term: 'sprint', description: 'Has pri in middle of name but not as a word',
|
||||
expectIdsContaining: ['103'] },
|
||||
|
||||
{ category: 'TEXT MATCH', term: 'secondary', description: 'Has secondary in name and/or is secondary',
|
||||
expectIdsContaining: ['201', '202', '203', '402'] },
|
||||
|
||||
{ category: 'TEXT MATCH', term: 'prism', description: 'Has pri as substring',
|
||||
expectIdsContaining: ['203'] },
|
||||
|
||||
// 4. PARTIAL SEARCHES AND EDGE CASES
|
||||
// The 'p' search should match shared guests with primary role AND match 'p' in text
|
||||
{ category: 'SINGLE CHAR', term: 'p', description: 'Single letter p',
|
||||
expectIdsContaining: ['101', '102', '103', '201', '202', '203', '301', '302', '401', '402'] },
|
||||
|
||||
// Same for 's'
|
||||
{ category: 'SINGLE CHAR', term: 's', description: 'Single letter s',
|
||||
expectIdsContaining: ['101', '102', '103', '201', '202', '203', '301', '302', '401', '402'] },
|
||||
|
||||
// Test pve prefix in node names
|
||||
{ category: 'SINGLE CHAR', term: 'pve', description: 'Text in all node IDs',
|
||||
expectIdsContaining: ['101', '102', '103', '201', '202', '203', '301', '302', '401', '402'] },
|
||||
|
||||
// Test a mix of properties and values
|
||||
{ category: 'TYPE SEARCH', term: 'qemu', description: 'VM type search',
|
||||
expectIdsContaining: ['101', '102', '103', '201', '301', '401', '402'] },
|
||||
|
||||
{ category: 'TYPE SEARCH', term: 'vm', description: 'VM alternate search',
|
||||
expectIdsContaining: ['101', '102', '103', '201', '301', '401', '402'] },
|
||||
|
||||
{ category: 'TYPE SEARCH', term: 'lxc', description: 'Container type search',
|
||||
expectIdsContaining: ['202', '203', '302'] },
|
||||
|
||||
// 5. COMBINED SEARCHES (AND LOGIC)
|
||||
{ category: 'COMBINED', term: 'primary running', description: 'Primary AND running status',
|
||||
expectIdsContaining: ['101', '102', '401'] },
|
||||
|
||||
{ category: 'COMBINED', term: 'sec stopped', description: 'Secondary AND stopped status',
|
||||
expectIdsContaining: ['202'] },
|
||||
|
||||
// 6. NEGATIVE TESTS - these should NOT match
|
||||
{ category: 'NEGATIVE', term: 'nonexistent', description: 'Term that appears nowhere',
|
||||
expectIdsContaining: [] }
|
||||
];
|
||||
|
||||
// Run the tests
|
||||
console.log(`Running ${testCases.length} comprehensive search test cases\n`);
|
||||
|
||||
let passCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
// Keep track of failures by category
|
||||
const failures = {};
|
||||
|
||||
testCases.forEach((testCase, index) => {
|
||||
console.log(`[${index + 1}/${testCases.length}] Testing "${testCase.term}" - ${testCase.description}`);
|
||||
|
||||
// Run the search
|
||||
const filteredData = getSortedAndFilteredData(
|
||||
guests,
|
||||
{ key: 'name', direction: 'asc' }, // Default sort
|
||||
{}, // No filters
|
||||
null, // Show all statuses
|
||||
[testCase.term], // Single search term as an array
|
||||
'', // No active search term
|
||||
metricsData,
|
||||
'all', // Show all guest types
|
||||
nodeData
|
||||
);
|
||||
|
||||
// Extract result IDs
|
||||
const resultIds = filteredData.map(guest => guest.id);
|
||||
|
||||
// Validate expected IDs are included
|
||||
const expectedIds = testCase.expectIdsContaining;
|
||||
const missingIds = expectedIds.filter(id => !resultIds.includes(id));
|
||||
const unexpectedIds = resultIds.filter(id => !expectedIds.includes(id));
|
||||
|
||||
const passed = missingIds.length === 0 &&
|
||||
(unexpectedIds.length === 0 || expectedIds.length === 0 && resultIds.length === 0);
|
||||
|
||||
if (passed) {
|
||||
console.log(` ✅ PASSED - Found ${resultIds.length} guests as expected`);
|
||||
passCount++;
|
||||
} else {
|
||||
console.log(` ❌ FAILED`);
|
||||
if (missingIds.length > 0) {
|
||||
console.log(` Missing IDs: ${missingIds.join(', ')}`);
|
||||
}
|
||||
if (unexpectedIds.length > 0) {
|
||||
console.log(` Unexpected IDs: ${unexpectedIds.join(', ')}`);
|
||||
}
|
||||
|
||||
// Add to failures by category
|
||||
const category = testCase.category;
|
||||
if (!failures[category]) {
|
||||
failures[category] = [];
|
||||
}
|
||||
failures[category].push({
|
||||
term: testCase.term,
|
||||
description: testCase.description,
|
||||
expected: expectedIds,
|
||||
actual: resultIds,
|
||||
missing: missingIds,
|
||||
unexpected: unexpectedIds
|
||||
});
|
||||
|
||||
failCount++;
|
||||
}
|
||||
|
||||
console.log(''); // Empty line for readability
|
||||
});
|
||||
|
||||
// Print summary
|
||||
console.log('===== TEST SUMMARY =====');
|
||||
console.log(`Total tests: ${testCases.length}`);
|
||||
console.log(`Passed: ${passCount} (${(passCount/testCases.length*100).toFixed(1)}%)`);
|
||||
console.log(`Failed: ${failCount} (${(failCount/testCases.length*100).toFixed(1)}%)`);
|
||||
|
||||
// Print failures by category
|
||||
if (failCount > 0) {
|
||||
console.log('\n===== FAILURES BY CATEGORY =====');
|
||||
Object.keys(failures).forEach(category => {
|
||||
console.log(`\n${category} - ${failures[category].length} failures:`);
|
||||
failures[category].forEach(failure => {
|
||||
console.log(` "${failure.term}" - ${failure.description}`);
|
||||
console.log(` Expected: ${failure.expected.join(', ')}`);
|
||||
console.log(` Actual: ${failure.actual.join(', ')}`);
|
||||
});
|
||||
});
|
||||
|
||||
// Provide advice for fixing issues
|
||||
console.log('\n===== TROUBLESHOOTING =====');
|
||||
|
||||
// Check specifically for 'pri' issues
|
||||
if (failures['ROLE STANDALONE']?.some(f => f.term === 'pri')) {
|
||||
console.log('\nIssue detected with "pri" searches:');
|
||||
console.log('1. Check if "pri" is being treated as a special case rather than a role indicator');
|
||||
console.log('2. Ensure proper handling of "pri" at the beginning of the search logic');
|
||||
console.log('3. Make sure word boundaries are properly enforced for "pri" matches');
|
||||
}
|
||||
|
||||
// Check for single character issues
|
||||
if (Object.keys(failures).includes('SINGLE CHAR')) {
|
||||
console.log('\nIssue detected with single character searches:');
|
||||
console.log('1. Single character searches should perform a full text search across all fields');
|
||||
console.log('2. Check that single character logic runs BEFORE role-specific logic');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
totalTests: testCases.length,
|
||||
passed: passCount,
|
||||
failed: failCount,
|
||||
failures
|
||||
};
|
||||
}
|
||||
|
||||
// Run the tests if executed directly
|
||||
if (typeof require !== 'undefined' && require.main === module) {
|
||||
runExhaustiveTests();
|
||||
}
|
||||
|
||||
module.exports = { runExhaustiveTests };
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Comprehensive Search Verification Script
|
||||
*
|
||||
* This script runs a battery of tests to verify that ALL search patterns work correctly.
|
||||
* It combines both standard tests and special test cases for edge conditions.
|
||||
*/
|
||||
|
||||
import { runSearchTests } from './searchTests.js';
|
||||
|
||||
console.log('===============================================================');
|
||||
console.log('= COMPREHENSIVE SEARCH VERIFICATION =');
|
||||
console.log('===============================================================');
|
||||
console.log('This script verifies ALL search patterns work correctly, including:');
|
||||
console.log('- Basic text searches');
|
||||
console.log('- Role-specific searches (pri, sec, primary, secondary)');
|
||||
console.log('- Column-based searches (role:pri, type:vm, etc.)');
|
||||
console.log('- Single character searches (p, s, v, etc.)');
|
||||
console.log('- Edge cases and potentially problematic patterns');
|
||||
console.log('\nRunning standard search test suite...');
|
||||
|
||||
// Execute all standard tests
|
||||
const results = runSearchTests();
|
||||
|
||||
// Output summary
|
||||
console.log('\n===============================================================');
|
||||
console.log('= VERIFICATION RESULTS =');
|
||||
console.log('===============================================================');
|
||||
|
||||
if (results.general.failed === 0 && results.role.filter(r => !r.passed).length === 0) {
|
||||
console.log('✅ ALL TESTS PASSED!');
|
||||
console.log(` - ${results.general.passed} general tests passed`);
|
||||
console.log(` - ${results.role.filter(r => r.passed).length} role-specific tests passed`);
|
||||
console.log('\nThe search functionality is working correctly for all patterns.');
|
||||
console.log('Key validations:');
|
||||
console.log(' - "pri" correctly returns primary guests');
|
||||
console.log(' - "sec" correctly returns secondary guests');
|
||||
console.log(' - Single-character searches work properly');
|
||||
console.log(' - Combined searches apply AND logic correctly');
|
||||
} else {
|
||||
console.log('❌ SOME TESTS FAILED!');
|
||||
console.log(` - ${results.general.failed} general tests failed`);
|
||||
console.log(` - ${results.role.filter(r => !r.passed).length} role-specific tests failed`);
|
||||
|
||||
// Show detailed failures
|
||||
console.log('\nFailed tests:');
|
||||
if (results.general.failed > 0) {
|
||||
results.general.details
|
||||
.filter(detail => !detail.passed)
|
||||
.forEach(detail => {
|
||||
console.log(` - ${detail.name}: ${detail.description}`);
|
||||
console.log(` Expected: ${JSON.stringify(detail.expectedIds)}`);
|
||||
console.log(` Actual: ${JSON.stringify(detail.actualIds)}`);
|
||||
});
|
||||
}
|
||||
|
||||
const failedRoleTests = results.role.filter(r => !r.passed);
|
||||
if (failedRoleTests.length > 0) {
|
||||
console.log('\nFailed role-specific tests:');
|
||||
failedRoleTests.forEach(r => {
|
||||
console.log(` - "${r.term}": Expected ${JSON.stringify(r.expectedIds)}, got ${JSON.stringify(r.resultIds)}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Execution instructions
|
||||
console.log('\n===============================================================');
|
||||
console.log('= HOW TO USE THIS VERIFICATION TOOL =');
|
||||
console.log('===============================================================');
|
||||
console.log('Run this script after making any changes to the search functionality:');
|
||||
console.log(' node frontend/src/utils/VerifyAllSearches.js');
|
||||
console.log('\nIf you add new search capabilities, update searchTests.js to include tests');
|
||||
console.log('for the new functionality.');
|
||||
|
||||
// Exit with appropriate code
|
||||
if (results.general.failed === 0 && results.role.filter(r => !r.passed).length === 0) {
|
||||
process.exit(0);
|
||||
} else {
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
// Debug script for testing 'pri' search issue
|
||||
|
||||
// Mock implementation of matchesTerm function for testing
|
||||
function matchesTerm(guest, termLower, nodeData) {
|
||||
// Prevent operations on undefined/null terms
|
||||
if (!termLower) return true;
|
||||
|
||||
// CASE 3: Standard role terminology
|
||||
// Matches exact role terms (not as part of other words) using standard terminology
|
||||
if (termLower === 'shared' || termLower === 'role') {
|
||||
return !!guest.shared;
|
||||
}
|
||||
|
||||
// Primary role terms: match whole words only, requires shared=true and isPrimary=true
|
||||
if (termLower === 'primary' || termLower === 'pri') {
|
||||
if (!guest.shared) return false;
|
||||
console.log(`Checking primary for ${guest.id}: primaryNode=${guest.primaryNode}, node=${guest.node}, matches=${guest.primaryNode === guest.node}`);
|
||||
return guest.primaryNode === guest.node;
|
||||
}
|
||||
|
||||
// Secondary role terms: match whole words only, requires shared=true and isPrimary=false
|
||||
if (termLower === 'secondary' || termLower === 'sec') {
|
||||
if (!guest.shared) return false;
|
||||
return guest.primaryNode !== guest.node;
|
||||
}
|
||||
|
||||
// CASE 5: Single character searches (including role abbreviations)
|
||||
if (termLower.length === 1) {
|
||||
const searchText = getFullSearchableText(guest, nodeData);
|
||||
console.log(`Single-char search for ${termLower} in ${guest.id}: "${searchText}"`);
|
||||
return searchText.includes(termLower);
|
||||
}
|
||||
|
||||
// Default: search in all text fields
|
||||
const searchText = getFullSearchableText(guest, nodeData);
|
||||
console.log(`Full search for ${termLower} in ${guest.id}: "${searchText}"`);
|
||||
return searchText.includes(termLower);
|
||||
}
|
||||
|
||||
// Improved function to get complete searchable text for a guest
|
||||
function getFullSearchableText(guest, nodeData) {
|
||||
// Include ALL searchable properties
|
||||
const nodeName = guest.node || '';
|
||||
|
||||
// Build full searchable text by concatenating ALL searchable fields
|
||||
const searchableFields = [
|
||||
guest.name || '',
|
||||
guest.id || '',
|
||||
guest.status || '',
|
||||
// Add VM/CT descriptive terms
|
||||
guest.type === 'qemu' ? 'vm virtual machine' : 'ct container',
|
||||
nodeName,
|
||||
// Add role descriptive terms if shared
|
||||
guest.shared ? (guest.primaryNode === guest.node ? 'primary pri p' : 'secondary sec s') : '',
|
||||
// Add shared indicator if applicable
|
||||
guest.shared ? 'shared role' : 'none',
|
||||
// Any other custom properties that should be searchable
|
||||
guest.description || '',
|
||||
guest.tags || ''
|
||||
];
|
||||
|
||||
// Join all fields with spaces and convert to lowercase
|
||||
const fullSearchText = searchableFields.join(' ').toLowerCase();
|
||||
return fullSearchText;
|
||||
}
|
||||
|
||||
// Mock test data - simplified version of actual app data
|
||||
const testGuests = [
|
||||
{
|
||||
id: '101',
|
||||
name: 'web-server',
|
||||
status: 'running',
|
||||
type: 'qemu',
|
||||
node: 'pve-prod-01',
|
||||
shared: true,
|
||||
primaryNode: 'pve-prod-01' // This is primary on this node
|
||||
},
|
||||
{
|
||||
id: '102',
|
||||
name: 'database',
|
||||
status: 'running',
|
||||
type: 'qemu',
|
||||
node: 'pve-prod-01',
|
||||
shared: true,
|
||||
primaryNode: 'pve-prod-01' // This is primary on this node
|
||||
},
|
||||
{
|
||||
id: '201',
|
||||
name: 'cache-server',
|
||||
status: 'stopped',
|
||||
type: 'qemu',
|
||||
node: 'pve-prod-02',
|
||||
shared: true,
|
||||
primaryNode: 'pve-prod-01' // This is secondary on this node
|
||||
}
|
||||
];
|
||||
|
||||
// Mock node data
|
||||
const nodeData = [
|
||||
{ id: 'pve-prod-01', name: 'Production Node 1' },
|
||||
{ id: 'pve-prod-02', name: 'Production Node 2' }
|
||||
];
|
||||
|
||||
// Test specific search terms directly
|
||||
function testSearch(searchTerm) {
|
||||
console.log(`\n===== TESTING SEARCH TERM: "${searchTerm}" =====`);
|
||||
|
||||
const result = testGuests.filter(guest => {
|
||||
const matches = matchesTerm(guest, searchTerm.toLowerCase(), nodeData);
|
||||
console.log(` Guest ${guest.id} (${guest.name}) matches ${searchTerm}? ${matches ? 'YES' : 'NO'}`);
|
||||
return matches;
|
||||
});
|
||||
|
||||
console.log(`\nResults for "${searchTerm}":`);
|
||||
console.log(` Found ${result.length} guests:`);
|
||||
|
||||
if (result.length === 0) {
|
||||
console.log(" NO RESULTS FOUND!");
|
||||
} else {
|
||||
result.forEach(guest => {
|
||||
console.log(` - ${guest.id}: ${guest.name} (${guest.node}, shared=${guest.shared}, primaryNode=${guest.primaryNode})`);
|
||||
console.log(` Primary? ${guest.primaryNode === guest.node ? 'YES' : 'NO'}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Print expected vs actual for primaries
|
||||
const expectedPrimaries = testGuests.filter(g => g.shared && g.primaryNode === g.node);
|
||||
console.log(`\nExpected primaries: ${expectedPrimaries.length} guests`);
|
||||
expectedPrimaries.forEach(g => console.log(` - ${g.id}: ${g.name}`));
|
||||
|
||||
// Verify if all primaries were found
|
||||
const allPrimariesFound = expectedPrimaries.every(
|
||||
expected => result.some(res => res.id === expected.id)
|
||||
);
|
||||
|
||||
console.log(`\nAll primaries found? ${allPrimariesFound ? 'YES ✅' : 'NO ❌'}`);
|
||||
|
||||
if (!allPrimariesFound) {
|
||||
console.log("Missing primaries:");
|
||||
expectedPrimaries.forEach(expected => {
|
||||
if (!result.some(res => res.id === expected.id)) {
|
||||
console.log(` - ${expected.id}: ${expected.name}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Test various forms of the primary search
|
||||
console.log("\n********** DEBUGGING 'PRI' SEARCH ISSUE **********");
|
||||
testSearch('pri');
|
||||
testSearch('primary');
|
||||
testSearch('p'); // Single character test
|
||||
|
||||
// Test role-prefixed searches to check if they work differently
|
||||
console.log("\n********** TESTING PREFIXED SEARCHES **********");
|
||||
testSearch('role:pri');
|
||||
testSearch('role:primary');
|
||||
|
||||
console.log("\n\n********** SEARCH IMPLEMENTATION DETAILS **********");
|
||||
// Print the relevant implementation from networkUtils.js
|
||||
console.log(`
|
||||
Search logic for 'pri' is implemented in networkUtils.js:
|
||||
|
||||
// Standard role terminology
|
||||
if (termLower === 'primary' || termLower === 'pri') {
|
||||
if (!guest.shared) return false;
|
||||
return guest.primaryNode === guest.node;
|
||||
}
|
||||
|
||||
and in getFullSearchableText:
|
||||
|
||||
guest.shared ? (guest.primaryNode === guest.node ? 'primary pri p' : 'secondary sec s') : '',
|
||||
`);
|
||||
@@ -0,0 +1,193 @@
|
||||
// Standalone test for 'pri' search
|
||||
// Simple script to test that 'pri' search works properly
|
||||
|
||||
// Mock implementation of required functions
|
||||
function matchesTerm(guest, termLower, nodeData) {
|
||||
// Prevent operations on undefined/null terms
|
||||
if (!termLower) return true;
|
||||
|
||||
// Handle column-specific searches
|
||||
if (termLower.includes(':')) {
|
||||
const [prefix, value] = termLower.split(':', 2);
|
||||
|
||||
if (prefix.trim().toLowerCase() === 'role') {
|
||||
const roleValue = (value || '').trim().toLowerCase();
|
||||
|
||||
// Non-shared checks
|
||||
if (roleValue === '-' || roleValue === 'none') {
|
||||
return !guest.shared;
|
||||
}
|
||||
|
||||
// Need to be shared for other role searches
|
||||
if (!guest.shared) return false;
|
||||
|
||||
const isPrimary = guest.primaryNode === guest.node;
|
||||
|
||||
// Primary checks
|
||||
if (roleValue === 'p' || roleValue.startsWith('pri') || roleValue === 'primary') {
|
||||
console.log(`Column search ${termLower} for ${guest.id}: isPrimary=${isPrimary}`);
|
||||
return isPrimary;
|
||||
}
|
||||
|
||||
// Secondary checks
|
||||
if (roleValue === 's' || roleValue.startsWith('sec') || roleValue === 'secondary') {
|
||||
return !isPrimary;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unknown column, just do text search
|
||||
return getFullSearchableText(guest).includes(termLower);
|
||||
}
|
||||
|
||||
// Standard role terminology
|
||||
if (termLower === 'primary' || termLower === 'pri') {
|
||||
console.log(`Standard term "${termLower}" check for ${guest.id}: shared=${guest.shared}, isPrimary=${guest.primaryNode === guest.node}`);
|
||||
if (!guest.shared) return false;
|
||||
return guest.primaryNode === guest.node;
|
||||
}
|
||||
|
||||
// Secondary role terms
|
||||
if (termLower === 'secondary' || termLower === 'sec') {
|
||||
if (!guest.shared) return false;
|
||||
return guest.primaryNode !== guest.node;
|
||||
}
|
||||
|
||||
// Single letter searches
|
||||
if (termLower.length === 1) {
|
||||
const searchText = getFullSearchableText(guest);
|
||||
console.log(`Single char search "${termLower}" for ${guest.id} in: "${searchText}"`);
|
||||
return searchText.includes(termLower);
|
||||
}
|
||||
|
||||
// Default text search
|
||||
return getFullSearchableText(guest).includes(termLower);
|
||||
}
|
||||
|
||||
function getFullSearchableText(guest) {
|
||||
// Build full searchable text
|
||||
const searchableFields = [
|
||||
guest.name || '',
|
||||
guest.id || '',
|
||||
guest.status || '',
|
||||
guest.type === 'qemu' ? 'vm virtual machine' : 'ct container',
|
||||
guest.node || '',
|
||||
guest.shared ? (guest.primaryNode === guest.node ? 'primary pri p' : 'secondary sec s') : '',
|
||||
guest.shared ? 'shared role' : 'none'
|
||||
];
|
||||
|
||||
return searchableFields.join(' ').toLowerCase();
|
||||
}
|
||||
|
||||
// Mock guests
|
||||
const guests = [
|
||||
{
|
||||
id: '101',
|
||||
name: 'web-server-primary',
|
||||
node: 'node1',
|
||||
shared: true,
|
||||
primaryNode: 'node1', // Primary role on current node
|
||||
status: 'running',
|
||||
type: 'qemu'
|
||||
},
|
||||
{
|
||||
id: '102',
|
||||
name: 'db-primary',
|
||||
node: 'node1',
|
||||
shared: true,
|
||||
primaryNode: 'node1', // Primary role on current node
|
||||
status: 'running',
|
||||
type: 'qemu'
|
||||
},
|
||||
{
|
||||
id: '201',
|
||||
name: 'web-server-secondary',
|
||||
node: 'node2',
|
||||
shared: true,
|
||||
primaryNode: 'node1', // Secondary role on current node (node2)
|
||||
status: 'running',
|
||||
type: 'qemu'
|
||||
},
|
||||
{
|
||||
id: '202',
|
||||
name: 'db-secondary',
|
||||
node: 'node2',
|
||||
shared: true,
|
||||
primaryNode: 'node1', // Secondary role on current node (node2)
|
||||
status: 'running',
|
||||
type: 'qemu'
|
||||
},
|
||||
{
|
||||
id: '301',
|
||||
name: 'standalone-app',
|
||||
node: 'node3',
|
||||
shared: false, // Not shared
|
||||
status: 'running',
|
||||
type: 'qemu'
|
||||
},
|
||||
{
|
||||
id: '302',
|
||||
name: 'prince-app', // Has "pri" in the name, but not primary
|
||||
node: 'node3',
|
||||
shared: false,
|
||||
status: 'running',
|
||||
type: 'qemu'
|
||||
}
|
||||
];
|
||||
|
||||
// Run tests for various search terms
|
||||
function runTest(searchTerm) {
|
||||
console.log(`\n===== TESTING SEARCH FOR: "${searchTerm}" =====`);
|
||||
|
||||
const results = guests.filter(guest => {
|
||||
const matches = matchesTerm(guest, searchTerm.toLowerCase(), []);
|
||||
const searchableText = getFullSearchableText(guest);
|
||||
|
||||
console.log(`Guest ${guest.id} (${guest.name}): ${matches ? 'MATCH' : 'NO MATCH'}`);
|
||||
console.log(` Shared: ${guest.shared}, Primary node: ${guest.primaryNode}, Current node: ${guest.node}`);
|
||||
console.log(` Is Primary on this node? ${guest.shared && guest.primaryNode === guest.node}`);
|
||||
console.log(` Searchable text: "${searchableText}"`);
|
||||
console.log(` Contains 'pri'? ${searchableText.includes('pri')}`);
|
||||
console.log(` Contains 'primary'? ${searchableText.includes('primary')}`);
|
||||
|
||||
return matches;
|
||||
});
|
||||
|
||||
console.log(`\nRESULTS FOR "${searchTerm}":`);
|
||||
console.log(` Found ${results.length} matches:`);
|
||||
|
||||
if (results.length > 0) {
|
||||
results.forEach(r => console.log(` - ${r.id}: ${r.name}`));
|
||||
} else {
|
||||
console.log(' NO MATCHES');
|
||||
}
|
||||
|
||||
// Check expectation - primary search should find primary nodes
|
||||
const expectedPrimaries = guests.filter(g => g.shared && g.primaryNode === g.node);
|
||||
if (searchTerm === 'pri' || searchTerm === 'primary' || searchTerm === 'role:pri' || searchTerm === 'role:primary') {
|
||||
const allPrimariesFound = expectedPrimaries.every(p => results.some(r => r.id === p.id));
|
||||
const onlyPrimariesFound = results.every(r => expectedPrimaries.some(p => p.id === r.id));
|
||||
|
||||
console.log(`\nTEST RESULTS:`);
|
||||
console.log(` Expected ${expectedPrimaries.length} primaries: ${expectedPrimaries.map(p => p.id).join(', ')}`);
|
||||
console.log(` All primary nodes found? ${allPrimariesFound ? 'YES ✅' : 'NO ❌'}`);
|
||||
console.log(` Only primary nodes found? ${onlyPrimariesFound ? 'YES ✅' : 'NO ❌'}`);
|
||||
|
||||
if (!allPrimariesFound || !onlyPrimariesFound) {
|
||||
console.log(`\n❌ TEST FAILED`);
|
||||
} else {
|
||||
console.log(`\n✅ TEST PASSED`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("🔍 TESTING 'PRI' SEARCH FUNCTIONALITY");
|
||||
console.log("===================================");
|
||||
|
||||
runTest('pri');
|
||||
runTest('primary');
|
||||
runTest('role:pri');
|
||||
runTest('role:primary');
|
||||
runTest('p'); // Should match primary guests and others containing 'p'
|
||||
runTest('prince'); // Should match guest 302 ("prince-app")
|
||||
Reference in New Issue
Block a user