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
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:
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* Regression tests for processModelData in components/settings.js
|
||||
*
|
||||
* Issue #3800: LM Studio model dropdown was empty because the frontend's
|
||||
* processModelData had hardcoded if-blocks for only ollama_models,
|
||||
* openai_models, anthropic_models, and openai_endpoint_models. The fix
|
||||
* (PR #3942) replaces the four hardcoded blocks with a generic loop
|
||||
* that lifts every <provider>_models array.
|
||||
*
|
||||
* Strategy
|
||||
* --------
|
||||
* processModelData is defined inside an IIFE in settings.js and never
|
||||
* exposed on window, so we cannot import settings.js and call it. Instead
|
||||
* we read the file as text, locate the loop block (the chunk between
|
||||
* `if (data.providers) {` and its matching `}`), and execute that exact
|
||||
* source against a synthetic `data` object inside a new Function. This
|
||||
* tests the literal shipped code, not a re-implementation.
|
||||
*
|
||||
* If the loop block can't be located (e.g. someone refactors the function
|
||||
* shape later), the locator throws and every test fails loudly — that is
|
||||
* the intended failure mode, not a fallback.
|
||||
*/
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
const SETTINGS_PATH = resolve(
|
||||
__dirname,
|
||||
'../../../src/local_deep_research/web/static/js/components/settings.js',
|
||||
);
|
||||
|
||||
/**
|
||||
* Extract the full `if (data.providers) { ... }` statement (guard
|
||||
* included) from settings.js by brace-matching. We need the guard so
|
||||
* that the missing-providers test exercises the same null-safety as
|
||||
* production.
|
||||
*/
|
||||
function extractProvidersLoop(source) {
|
||||
const marker = 'if (data.providers) {';
|
||||
const start = source.indexOf(marker);
|
||||
if (start < 0) {
|
||||
throw new Error(
|
||||
'Could not find `if (data.providers) {` in settings.js — ' +
|
||||
'the fix may have been refactored. Update this test.',
|
||||
);
|
||||
}
|
||||
const openBrace = start + marker.length - 1; // index of `{`
|
||||
let depth = 0;
|
||||
let i = openBrace;
|
||||
for (; i < source.length; i++) {
|
||||
const ch = source[i];
|
||||
if (ch === '{') depth++;
|
||||
else if (ch === '}') {
|
||||
depth--;
|
||||
if (depth === 0) break;
|
||||
}
|
||||
}
|
||||
if (depth !== 0) {
|
||||
throw new Error('Brace mismatch when extracting providers loop.');
|
||||
}
|
||||
// Return the whole statement including the `if (...)` guard.
|
||||
return source.slice(start, i + 1);
|
||||
}
|
||||
|
||||
const SOURCE = readFileSync(SETTINGS_PATH, 'utf8');
|
||||
const LOOP_BODY = extractProvidersLoop(SOURCE);
|
||||
|
||||
/**
|
||||
* Run the extracted loop body against `data`, returning the populated
|
||||
* formattedModels array. Mirrors the surrounding processModelData
|
||||
* scaffolding (formattedModels declaration, SafeLogger stub).
|
||||
*/
|
||||
function runLoop(data) {
|
||||
const formattedModels = [];
|
||||
const SafeLogger = { log: () => {}, warn: () => {}, error: () => {} };
|
||||
// The loop body references `data`, `formattedModels`, and `SafeLogger`.
|
||||
// eslint-disable-next-line no-new-func
|
||||
const fn = new Function(
|
||||
'data', 'formattedModels', 'SafeLogger',
|
||||
LOOP_BODY,
|
||||
);
|
||||
fn(data, formattedModels, SafeLogger);
|
||||
return formattedModels;
|
||||
}
|
||||
|
||||
describe('processModelData providers loop (issue #3800)', () => {
|
||||
it('lifts lmstudio_models tagged as LMSTUDIO (the bug)', () => {
|
||||
const data = {
|
||||
providers: {
|
||||
lmstudio_models: [
|
||||
{ value: 'qwen-7b', label: 'qwen-7b' },
|
||||
{ value: 'phi-3', label: 'Phi 3' },
|
||||
],
|
||||
},
|
||||
};
|
||||
const out = runLoop(data);
|
||||
expect(out).toHaveLength(2);
|
||||
expect(out[0]).toEqual({
|
||||
value: 'qwen-7b',
|
||||
label: 'qwen-7b',
|
||||
provider: 'LMSTUDIO',
|
||||
});
|
||||
expect(out[1].provider).toBe('LMSTUDIO');
|
||||
});
|
||||
|
||||
it('lifts llamacpp_models tagged as LLAMACPP', () => {
|
||||
const data = {
|
||||
providers: {
|
||||
llamacpp_models: [
|
||||
{ value: 'llama-3-8b.gguf', label: 'Llama 3 8B' },
|
||||
],
|
||||
},
|
||||
};
|
||||
const out = runLoop(data);
|
||||
expect(out).toEqual([
|
||||
{
|
||||
value: 'llama-3-8b.gguf',
|
||||
label: 'Llama 3 8B',
|
||||
provider: 'LLAMACPP',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('still handles ollama_models (backwards compat)', () => {
|
||||
const data = {
|
||||
providers: {
|
||||
ollama_models: [{ value: 'llama3', label: 'Llama 3' }],
|
||||
},
|
||||
};
|
||||
const out = runLoop(data);
|
||||
expect(out).toEqual([
|
||||
{ value: 'llama3', label: 'Llama 3', provider: 'OLLAMA' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('preserves OPENAI_ENDPOINT casing (underscore in tag)', () => {
|
||||
const data = {
|
||||
providers: {
|
||||
openai_endpoint_models: [
|
||||
{ value: 'gpt-x', label: 'gpt-x' },
|
||||
],
|
||||
},
|
||||
};
|
||||
const out = runLoop(data);
|
||||
expect(out[0].provider).toBe('OPENAI_ENDPOINT');
|
||||
});
|
||||
|
||||
it('lifts multiple providers in one response', () => {
|
||||
const data = {
|
||||
providers: {
|
||||
ollama_models: [{ value: 'llama3', label: 'Llama 3' }],
|
||||
lmstudio_models: [{ value: 'qwen-7b', label: 'qwen-7b' }],
|
||||
openai_models: [{ value: 'gpt-4', label: 'GPT-4' }],
|
||||
},
|
||||
};
|
||||
const out = runLoop(data);
|
||||
const providers = out.map(m => m.provider).sort();
|
||||
expect(providers).toEqual(['LMSTUDIO', 'OLLAMA', 'OPENAI']);
|
||||
expect(out).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('handles empty providers dict without errors', () => {
|
||||
const out = runLoop({ providers: {} });
|
||||
expect(out).toEqual([]);
|
||||
});
|
||||
|
||||
it('handles missing providers (undefined) without errors', () => {
|
||||
const out = runLoop({});
|
||||
expect(out).toEqual([]);
|
||||
});
|
||||
|
||||
it('skips empty arrays', () => {
|
||||
const out = runLoop({ providers: { lmstudio_models: [] } });
|
||||
expect(out).toEqual([]);
|
||||
});
|
||||
|
||||
it('skips non-array values (defensive)', () => {
|
||||
const out = runLoop({
|
||||
providers: {
|
||||
lmstudio_models: 'not an array',
|
||||
ollama_models: [{ value: 'a', label: 'A' }],
|
||||
},
|
||||
});
|
||||
expect(out).toHaveLength(1);
|
||||
expect(out[0].provider).toBe('OLLAMA');
|
||||
});
|
||||
|
||||
it('ignores keys without _models suffix', () => {
|
||||
const out = runLoop({
|
||||
providers: {
|
||||
some_other_key: [{ value: 'x', label: 'x' }],
|
||||
lmstudio_models: [{ value: 'qwen', label: 'qwen' }],
|
||||
},
|
||||
});
|
||||
expect(out).toHaveLength(1);
|
||||
expect(out[0].provider).toBe('LMSTUDIO');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user