Files
wehub-resource-sync 7a0da7932b
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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

326 lines
13 KiB
JavaScript

/**
* API Key Inputs Test
* Tests the API key input fields on the research form for cloud providers
* CI-compatible: Works in both local and CI environments
*/
const puppeteer = require('puppeteer');
const AuthHelper = require('./auth_helper');
const { getPuppeteerLaunchOptions } = require('./puppeteer_config');
const fs = require('fs');
const path = require('path');
// API key provider configurations
const API_KEY_PROVIDERS = [
{ provider: 'OPENAI', containerId: 'openai_api_key_container', inputId: 'openai_api_key', settingKey: 'llm.openai.api_key' },
{ provider: 'ANTHROPIC', containerId: 'anthropic_api_key_container', inputId: 'anthropic_api_key', settingKey: 'llm.anthropic.api_key' },
{ provider: 'GOOGLE', containerId: 'google_api_key_container', inputId: 'google_api_key', settingKey: 'llm.google.api_key' },
{ provider: 'OPENROUTER', containerId: 'openrouter_api_key_container', inputId: 'openrouter_api_key', settingKey: 'llm.openrouter.api_key' },
{ provider: 'XAI', containerId: 'xai_api_key_container', inputId: 'xai_api_key', settingKey: 'llm.xai.api_key' },
{ provider: 'IONOS', containerId: 'ionos_api_key_container', inputId: 'ionos_api_key', settingKey: 'llm.ionos.api_key' },
{ provider: 'OPENAI_ENDPOINT', containerId: 'openai_endpoint_api_key_container', inputId: 'openai_endpoint_api_key', settingKey: 'llm.openai_endpoint.api_key' },
{ provider: 'OLLAMA', containerId: 'ollama_api_key_container', inputId: 'ollama_api_key', settingKey: 'llm.ollama.api_key' },
];
// Local providers that should NOT show API key fields (except Ollama which has optional key)
const LOCAL_PROVIDERS_NO_KEY = ['LMSTUDIO', 'LLAMACPP'];
async function testApiKeyInputs() {
const isCI = !!process.env.CI;
console.log(`\n🧪 Running API Key Inputs test (CI mode: ${isCI})\n`);
// Create screenshots directory if it doesn't exist
const screenshotsDir = path.join(__dirname, 'screenshots');
if (!fs.existsSync(screenshotsDir)) {
fs.mkdirSync(screenshotsDir, { recursive: true });
}
const browser = await puppeteer.launch(getPuppeteerLaunchOptions());
const page = await browser.newPage();
const baseUrl = 'http://127.0.0.1:5000';
// Increase default timeout in CI
if (isCI) {
page.setDefaultTimeout(60000);
page.setDefaultNavigationTimeout(60000);
}
let testsPassed = 0;
let testsFailed = 0;
function logTest(name, passed, message = '') {
if (passed) {
console.log(` ✅ ${name}${message ? ': ' + message : ''}`);
testsPassed++;
} else {
console.log(` ❌ ${name}${message ? ': ' + message : ''}`);
testsFailed++;
}
}
try {
// Authenticate
console.log('📋 Setup: Authenticating...');
const authHelper = new AuthHelper(page, baseUrl);
const testUser = {
username: `apikey_test_${Date.now()}`,
password: 'TestPass123!'
};
await authHelper.ensureAuthenticatedWithTimeout();
console.log('✅ Authenticated\n');
// Navigate to research page
console.log('📄 Navigating to research page...');
await page.goto(`${baseUrl}/`, {
waitUntil: 'networkidle2',
timeout: 30000
});
const currentUrl = page.url();
if (currentUrl.includes('/auth/login')) {
throw new Error('Redirected to login - not authenticated');
}
console.log('✅ Research page loaded\n');
// ===== TEST GROUP 1: API Key HTML Elements Exist =====
console.log('📋 Test Group 1: API Key HTML Elements Exist');
for (const config of API_KEY_PROVIDERS) {
const containerExists = await page.$(`#${config.containerId}`) !== null;
const inputExists = await page.$(`#${config.inputId}`) !== null;
logTest(
`${config.provider} API key elements`,
containerExists && inputExists,
containerExists && inputExists ? 'container and input found' : 'missing elements'
);
}
// ===== TEST GROUP 2: API Key Inputs Are Password Type =====
console.log('\n📋 Test Group 2: API Key Inputs Are Password Type');
for (const config of API_KEY_PROVIDERS) {
const inputType = await page.$eval(`#${config.inputId}`, el => el.type).catch(() => null);
logTest(
`${config.provider} input is password type`,
inputType === 'password',
inputType === 'password' ? 'correctly masked' : `got type: ${inputType}`
);
}
// ===== TEST GROUP 3: All API Key Containers Initially Hidden =====
console.log('\n📋 Test Group 3: API Key Containers Initially Hidden');
for (const config of API_KEY_PROVIDERS) {
const displayStyle = await page.$eval(`#${config.containerId}`, el => {
return window.getComputedStyle(el).display;
}).catch(() => 'error');
// Initially all should be hidden (display: none)
logTest(
`${config.provider} container initially hidden`,
displayStyle === 'none',
displayStyle === 'none' ? 'correctly hidden' : `display: ${displayStyle}`
);
}
// ===== TEST GROUP 4: Ensure Advanced Options Expanded =====
console.log('\n📋 Test Group 4: Ensure Advanced Options Expanded');
const advancedToggle = await page.$('.ldr-advanced-options-toggle');
if (advancedToggle) {
// Check if panel is already expanded (default open)
const panel = await page.$('.ldr-advanced-options-panel');
const isAlreadyExpanded = panel ? await page.evaluate(el => {
const style = window.getComputedStyle(el);
return style.visibility !== 'hidden' && style.opacity !== '0';
}, panel) : false;
if (!isAlreadyExpanded) {
await advancedToggle.click();
await page.waitForTimeout(500); // Wait for animation
}
logTest('Advanced options expanded', true, isAlreadyExpanded ? 'already open by default' : 'expanded via click');
} else {
logTest('Advanced options toggle', false, 'toggle not found');
}
// ===== TEST GROUP 5: Provider Selection Shows Correct API Key Field =====
console.log('\n📋 Test Group 5: Provider Selection Shows Correct API Key Field');
const providerSelect = await page.$('#model_provider');
if (!providerSelect) {
logTest('Provider select', false, 'provider dropdown not found');
} else {
for (const config of API_KEY_PROVIDERS) {
// Select the provider
await page.select('#model_provider', config.provider);
await page.waitForTimeout(300); // Wait for visibility toggle
// Check that this provider's API key container is visible
const isVisible = await page.$eval(`#${config.containerId}`, el => {
return window.getComputedStyle(el).display !== 'none';
}).catch(() => false);
logTest(
`${config.provider} shows API key field when selected`,
isVisible,
isVisible ? 'field visible' : 'field still hidden'
);
// Check that other API key containers are hidden
for (const otherConfig of API_KEY_PROVIDERS) {
if (otherConfig.provider !== config.provider) {
const otherHidden = await page.$eval(`#${otherConfig.containerId}`, el => {
return window.getComputedStyle(el).display === 'none';
}).catch(() => true);
if (!otherHidden) {
logTest(
`${otherConfig.provider} hidden when ${config.provider} selected`,
false,
'should be hidden but is visible'
);
}
}
}
}
}
// ===== TEST GROUP 6: Local Providers Don't Show API Key (except Ollama) =====
console.log('\n📋 Test Group 6: Local Providers Without API Key Fields');
for (const localProvider of LOCAL_PROVIDERS_NO_KEY) {
// Try to select the provider (may not exist in dropdown)
try {
await page.select('#model_provider', localProvider);
await page.waitForTimeout(300);
// Check that no cloud API key containers are visible
let anyVisible = false;
for (const config of API_KEY_PROVIDERS) {
// Skip Ollama since it has optional API key
if (config.provider === 'OLLAMA') continue;
const isVisible = await page.$eval(`#${config.containerId}`, el => {
return window.getComputedStyle(el).display !== 'none';
}).catch(() => false);
if (isVisible) {
anyVisible = true;
break;
}
}
logTest(
`${localProvider} hides cloud API key fields`,
!anyVisible,
anyVisible ? 'some API key fields visible' : 'all hidden correctly'
);
} catch {
// Provider may not be in the dropdown, that's OK
console.log(` ⏭️ ${localProvider} provider not in dropdown, skipping`);
}
}
// ===== TEST GROUP 7: API Key Input Accepts Text =====
console.log('\n📋 Test Group 7: API Key Input Accepts Text');
// Select OpenAI provider
await page.select('#model_provider', 'OPENAI');
await page.waitForTimeout(300);
const testApiKey = 'sk-test-key-12345';
await page.type('#openai_api_key', testApiKey);
const inputValue = await page.$eval('#openai_api_key', el => el.value);
logTest(
'API key input accepts text',
inputValue === testApiKey,
inputValue === testApiKey ? 'value stored correctly' : `got: ${inputValue}`
);
// ===== TEST GROUP 8: API Key Persists After Provider Switch and Back =====
console.log('\n📋 Test Group 8: API Key Value Persistence');
// Switch to another provider
await page.select('#model_provider', 'ANTHROPIC');
await page.waitForTimeout(300);
// Switch back to OpenAI
await page.select('#model_provider', 'OPENAI');
await page.waitForTimeout(300);
const persistedValue = await page.$eval('#openai_api_key', el => el.value);
logTest(
'API key persists after provider switch',
persistedValue === testApiKey,
persistedValue === testApiKey ? 'value preserved' : `got: ${persistedValue}`
);
// ===== TEST GROUP 9: API Key Save Handler Fires =====
console.log('\n📋 Test Group 9: API Key Save Handler');
// Listen for network requests to verify save
let saveRequestFired = false;
page.on('request', request => {
if (request.url().includes('/settings/api/') && request.method() === 'PUT') {
saveRequestFired = true;
}
});
// Clear and type a new value, then blur to trigger save
await page.$eval('#openai_api_key', el => { el.value = ''; });
await page.type('#openai_api_key', 'sk-new-test-key');
await page.click('#model_provider'); // Click elsewhere to trigger blur/change
await page.waitForTimeout(500);
logTest(
'API key save request fires on change',
saveRequestFired,
saveRequestFired ? 'save request detected' : 'no save request detected'
);
// Take a screenshot of the final state
await page.screenshot({
path: path.join(screenshotsDir, 'api_key_inputs_final.png'),
fullPage: true
});
} catch (error) {
console.error(`\n❌ Test error: ${error.message}`);
// Take error screenshot
try {
await page.screenshot({
path: path.join(screenshotsDir, 'api_key_inputs_error.png'),
fullPage: true
});
} catch {
// Ignore screenshot errors
}
testsFailed++;
} finally {
await browser.close();
}
// Summary
console.log('\n' + '='.repeat(50));
console.log(`📊 Test Summary: ${testsPassed} passed, ${testsFailed} failed`);
console.log('='.repeat(50));
if (testsFailed > 0) {
console.log('\n❌ Some tests failed');
process.exit(1);
} else {
console.log('\n✅ All tests passed!');
process.exit(0);
}
}
// Run the tests
testApiKeyInputs().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});