chore: import upstream snapshot with attribution
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:08:55 +08:00
commit 7a0da7932b
2985 changed files with 1049377 additions and 0 deletions
@@ -0,0 +1,506 @@
const puppeteer = require('puppeteer');
const AuthHelper = require('./auth_helper');
const { getPuppeteerLaunchOptions } = require('./puppeteer_config');
// Color codes for terminal output
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m'
};
function log(message, type = 'info') {
const timestamp = new Date().toISOString();
const prefix = {
info: `${colors.blue}`,
success: `${colors.green}`,
error: `${colors.red}`,
warn: `${colors.yellow}`,
test: `${colors.magenta}`
}[type] || '';
console.log(`${prefix} [${timestamp}] ${message}${colors.reset}`);
}
// Test configuration
const TEST_CONFIG = {
baseUrl: 'http://localhost:5000',
apiKeys: {
initial: 'sk-test-initial-key-123456789',
changed: 'sk-test-changed-key-987654321',
final: 'sk-test-final-key-555555555'
},
timeout: 30000,
headless: process.env.SHOW_BROWSER !== '1',
slowMo: process.env.SHOW_BROWSER === '1' ? 100 : 0
};
// Main test function
async function runComprehensiveApiKeyTest() {
let browser;
let page;
let authHelper;
const timestamp = Date.now();
const testUsername = `api_key_test_${timestamp}`;
const testPassword = 'TestP@ssw0rd123';
const testResults = {
authentication: false,
settingsPageAccess: false,
apiKeyFieldFound: false,
enterKeySave: false,
apiSave: false,
persistence: false,
multipleChanges: false
};
const apiCalls = [];
try {
log(`${colors.bright}=== Starting Comprehensive API Key Settings Test ===${colors.reset}`, 'info');
log(`Test user: ${testUsername}`, 'info');
// Launch browser
browser = await puppeteer.launch(getPuppeteerLaunchOptions());
page = await browser.newPage();
await page.setViewport({ width: 1920, height: 1080 });
// Log browser console errors
page.on('console', msg => {
if (msg.type() === 'error') {
log(`Browser console: ${msg.text()}`, 'error');
}
});
// Set up request interception to monitor API calls
await page.setRequestInterception(true);
page.on('request', request => {
const url = request.url();
if (url.includes('/api/settings') || url.includes('/auth/')) {
apiCalls.push({
method: request.method(),
url,
timestamp: new Date().toISOString()
});
}
request.continue();
});
// Initialize auth helper
authHelper = new AuthHelper(page, TEST_CONFIG.baseUrl);
// Test 1: Authentication
log('Test 1: User Registration and Authentication', 'test');
try {
await authHelper.register(testUsername, testPassword);
const isAuthenticated = await authHelper.isLoggedIn();
if (isAuthenticated) {
testResults.authentication = true;
log('Authentication successful', 'success');
} else {
throw new Error('Failed to verify authentication');
}
} catch (error) {
log(`Authentication failed: ${error.message}`, 'error');
throw error;
}
// Test 2: Navigate to Settings Page
log('Test 2: Accessing Settings Page', 'test');
try {
await page.goto(`${TEST_CONFIG.baseUrl}/settings`, {
waitUntil: 'domcontentloaded',
timeout: TEST_CONFIG.timeout
});
// Verify we're on the settings page
const currentUrl = page.url();
if (!currentUrl.includes('/settings')) {
throw new Error(`Not on settings page. Current URL: ${currentUrl}`);
}
// Wait for settings to load
await page.waitForSelector('input, select, textarea', { timeout: 10000 });
testResults.settingsPageAccess = true;
log('Settings page loaded successfully', 'success');
// Take screenshot
await page.screenshot({
path: `/tmp/api_key_test_settings_${timestamp}.png`,
fullPage: true
});
log(`Screenshot saved: /tmp/api_key_test_settings_${timestamp}.png`, 'info');
} catch (error) {
log(`Failed to access settings page: ${error.message}`, 'error');
throw error;
}
// Test 3: Find API Key Field
log('Test 3: Locating OpenAI Endpoint API Key Field', 'test');
let apiKeyInput = null;
const apiKeySelectors = [
'input[name="llm.openai_endpoint.api_key"]',
'input[id="llm.openai_endpoint.api_key"]',
'input[data-setting="llm.openai_endpoint.api_key"]',
'#llm\\.openai_endpoint\\.api_key',
'input[type="password"][name*="openai"][name*="api_key"]',
'input[type="text"][name*="openai"][name*="api_key"]'
];
// First, check if we need to navigate to a specific section
const sections = await page.evaluate(() => {
const links = Array.from(document.querySelectorAll('a[href*="settings"], .nav-link, [role="tab"]'));
return links.map(link => ({
text: link.textContent.trim(),
href: link.href,
isActive: link.classList.contains('active')
}));
});
log(`Found ${sections.length} settings sections`, 'info');
// Look for LLM or API section
const llmSection = sections.find(s =>
s.text.toLowerCase().includes('llm') ||
s.text.toLowerCase().includes('api') ||
s.text.toLowerCase().includes('model')
);
if (llmSection && !llmSection.isActive) {
log(`Clicking on section: ${llmSection.text}`, 'info');
await page.evaluate((href) => {
const link = document.querySelector(`a[href="${href}"]`);
if (link) link.click();
}, llmSection.href);
await new Promise(resolve => setTimeout(resolve, 1000));
}
// Try to find the API key field
for (const selector of apiKeySelectors) {
try {
apiKeyInput = await page.$(selector);
if (apiKeyInput) {
const isVisible = await apiKeyInput.evaluate(el => {
const rect = el.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
});
if (isVisible) {
log(`Found API key field with selector: ${selector}`, 'success');
testResults.apiKeyFieldFound = true;
break;
}
}
} catch {
// Continue with next selector
}
}
if (!apiKeyInput || !testResults.apiKeyFieldFound) {
// Try expanding collapsible sections
log('API key field not immediately visible, looking for expandable sections', 'info');
const expandables = await page.$$('[data-toggle="collapse"], .accordion-button, details summary');
for (const expandable of expandables) {
const text = await expandable.evaluate(el => el.textContent);
if (text.toLowerCase().includes('llm') || text.toLowerCase().includes('openai')) {
log(`Expanding section: ${text.trim()}`, 'info');
await expandable.click();
await new Promise(resolve => setTimeout(resolve, 500));
}
}
// Try finding the field again
for (const selector of apiKeySelectors) {
apiKeyInput = await page.$(selector);
if (apiKeyInput) {
const isVisible = await apiKeyInput.evaluate(el => el.offsetParent !== null);
if (isVisible) {
log(`Found API key field after expanding sections: ${selector}`, 'success');
testResults.apiKeyFieldFound = true;
break;
}
}
}
}
if (!testResults.apiKeyFieldFound) {
throw new Error('Could not find OpenAI Endpoint API key field');
}
// Test 4: Enter Key Save Functionality
log('Test 4: Testing Enter Key Save (Individual Field)', 'test');
try {
// Wait for event handlers to be attached
log('Waiting for event handlers to be attached...', 'info');
await page.waitForFunction(() => {
const input = document.querySelector('input[name="llm.openai_endpoint.api_key"]');
if (!input) return false;
// Check if the input has data indicating handlers are attached
// The settings.js adds event listeners dynamically
return true; // For now, just wait a bit
}, { timeout: 5000 }).catch(() => {
log('Handler wait timed out', 'warn');
});
// Additional wait to ensure handlers are ready
await new Promise(resolve => setTimeout(resolve, 2000));
// Focus the input first
await apiKeyInput.focus();
// Clear the field completely
await page.keyboard.down('Control');
await page.keyboard.press('a');
await page.keyboard.up('Control');
await page.keyboard.press('Backspace');
// Type the new value
await apiKeyInput.type(TEST_CONFIG.apiKeys.initial);
// Small delay to ensure typing is complete
await new Promise(resolve => setTimeout(resolve, 100));
// Get initial value
const valueBefore = await apiKeyInput.evaluate(el => el.value);
log(`Typed value: ${valueBefore}`, 'info');
// Press Enter to save
log('Pressing Enter to save...', 'info');
await page.keyboard.press('Enter');
// Wait for save operation to start and complete
// First wait for the saving class to appear
await page.waitForFunction(
() => {
const input = document.querySelector('input[name="llm.openai_endpoint.api_key"]');
const container = input?.closest('.form-group, .settings-item') || input;
return container?.classList.contains('saving');
},
{ timeout: 5000 }
).catch(() => log('No saving indicator detected', 'warn'));
// Then wait for save-success class or toast message
const successIndicators = await page.waitForFunction(
() => {
const indicators = [];
// Check for save-success class on input
const input = document.querySelector('input[name="llm.openai_endpoint.api_key"]');
if (input && input.classList.contains('save-success')) {
indicators.push('Input has save-success class');
}
// Check for toast notifications
const toastMessage = document.querySelector('.toast-message, .alert-success');
if (toastMessage && toastMessage.textContent.toLowerCase().includes('saved')) {
indicators.push(toastMessage.textContent.trim());
}
// Check if ui.showMessage was called (look for any success toast)
const toasts = document.querySelectorAll('[class*="toast"][class*="success"], .notification.success');
toasts.forEach(toast => {
if (toast.offsetParent !== null) { // Is visible
indicators.push('Success toast visible');
}
});
return indicators.length > 0 ? indicators : null;
},
{ timeout: 5000 }
).then(result => result.jsonValue())
.catch(() => []);
if (successIndicators.length > 0) {
log(`Success indicators found: ${successIndicators.join(', ')}`, 'success');
testResults.enterKeySave = true;
} else {
log('No immediate success indicators found, checking persistence...', 'warn');
}
// Verify by reloading the page
log('Reloading page to check persistence...', 'info');
await page.reload({ waitUntil: 'domcontentloaded' });
// Find the field again
apiKeyInput = null;
for (const selector of apiKeySelectors) {
apiKeyInput = await page.$(selector);
if (apiKeyInput) break;
}
if (apiKeyInput) {
const valueAfterReload = await apiKeyInput.evaluate(el => el.value);
if (valueAfterReload && valueAfterReload.includes(TEST_CONFIG.apiKeys.initial.slice(-5))) {
log('Value persisted after reload!', 'success');
testResults.enterKeySave = true;
testResults.persistence = true;
} else {
log(`Value after reload: ${valueAfterReload || 'empty'}`, 'warn');
}
}
} catch (error) {
log(`Enter key save test failed: ${error.message}`, 'error');
}
// Test 5: API-based Save
log('Test 5: Testing API-based Settings Save', 'test');
try {
const apiResponse = await page.evaluate(async (apiKey) => {
const response = await fetch('/settings/api/llm.openai_endpoint.api_key', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify({
value: apiKey
})
});
return {
ok: response.ok,
status: response.status,
data: await response.json()
};
}, TEST_CONFIG.apiKeys.changed);
if (apiResponse.ok) {
log(`API save successful: ${apiResponse.data.message || 'OK'}`, 'success');
testResults.apiSave = true;
// Verify the change
const verifyResponse = await page.evaluate(async () => {
const response = await fetch('/settings/api/llm.openai_endpoint.api_key', {
credentials: 'same-origin'
});
return await response.json();
});
const apiKeySetting = verifyResponse;
if (apiKeySetting && apiKeySetting.value) {
log(`API key verified via API: ***${apiKeySetting.value.slice(-5)}`, 'success');
}
} else {
log(`API save failed: ${apiResponse.status} - ${JSON.stringify(apiResponse.data)}`, 'error');
}
} catch (error) {
log(`API save test failed: ${error.message}`, 'error');
}
// Test 6: Multiple Changes and Final Verification
log('Test 6: Testing Multiple Changes', 'test');
try {
// Reload to get fresh state
await page.reload({ waitUntil: 'domcontentloaded' });
// Find field again
apiKeyInput = null;
for (const selector of apiKeySelectors) {
apiKeyInput = await page.$(selector);
if (apiKeyInput) break;
}
if (apiKeyInput) {
// Make final change
await apiKeyInput.click({ clickCount: 3 });
await page.keyboard.press('Backspace');
await apiKeyInput.type(TEST_CONFIG.apiKeys.final);
// Try different save methods
log('Trying blur event (Tab)', 'info');
await page.keyboard.press('Tab');
await new Promise(resolve => setTimeout(resolve, 1000));
log('Clicking elsewhere on page', 'info');
await page.click('body');
await new Promise(resolve => setTimeout(resolve, 1000));
// Final verification
const finalCheck = await page.evaluate(async () => {
const response = await fetch('/settings/api/llm.openai_endpoint.api_key', {
credentials: 'same-origin'
});
return await response.json();
});
const finalApiKeySetting = finalCheck;
if (finalApiKeySetting && finalApiKeySetting.value) {
log(`Final API key value: ***${finalApiKeySetting.value.slice(-5)}`, 'info');
if (finalApiKeySetting.value.includes(TEST_CONFIG.apiKeys.final.slice(-5)) ||
finalApiKeySetting.value.includes(TEST_CONFIG.apiKeys.changed.slice(-5))) {
testResults.multipleChanges = true;
log('Multiple changes test passed', 'success');
}
}
}
} catch (error) {
log(`Multiple changes test failed: ${error.message}`, 'error');
}
// Take final screenshot
await page.screenshot({
path: `/tmp/api_key_test_final_${timestamp}.png`,
fullPage: false
});
log(`Final screenshot saved: /tmp/api_key_test_final_${timestamp}.png`, 'info');
} catch (error) {
log(`Test suite failed: ${error.message}`, 'error');
console.error(error);
// Take error screenshot
if (page) {
await page.screenshot({
path: `/tmp/api_key_test_error_${timestamp}.png`,
fullPage: true
});
log(`Error screenshot saved: /tmp/api_key_test_error_${timestamp}.png`, 'info');
}
} finally {
// Generate test report
log('\n' + colors.bright + '=== Test Results Summary ===' + colors.reset, 'info');
const totalTests = Object.keys(testResults).length;
const passedTests = Object.values(testResults).filter(v => v).length;
const failedTests = totalTests - passedTests;
for (const [test, result] of Object.entries(testResults)) {
const status = result ? `${colors.green}PASS${colors.reset}` : `${colors.red}FAIL${colors.reset}`;
const testName = test.replace(/([A-Z])/g, ' $1').trim();
log(`${testName}: ${status}`, result ? 'success' : 'error');
}
log(`\nTotal: ${totalTests} | Passed: ${passedTests} | Failed: ${failedTests}`, 'info');
// Log API calls made during test
if (apiCalls.length > 0) {
log('\n' + colors.bright + '=== API Calls Made ===' + colors.reset, 'info');
apiCalls.forEach(call => {
log(`${call.method} ${call.url}`, 'info');
});
}
// Clean up
if (browser) {
await browser.close();
log('Browser closed', 'info');
}
// Exit with appropriate code
process.exit(failedTests > 0 ? 1 : 0);
}
}
// Run the test
runComprehensiveApiKeyTest().catch(error => {
console.error('Unhandled error:', error);
process.exit(1);
});