2026-06-09 00:15:54 +08:00
|
|
|
import { describe, expect, it, afterEach } from 'vitest';
|
2026-04-30 13:07:37 +03:00
|
|
|
import express from 'express';
|
|
|
|
|
import request from 'supertest';
|
|
|
|
|
|
|
|
|
|
import { registerTtsRoutes } from './routes.js';
|
2026-06-09 00:15:54 +08:00
|
|
|
import { normalizeCustomOpenAIBaseURL } from './base-url.js';
|
2026-04-30 13:07:37 +03:00
|
|
|
|
|
|
|
|
const createApp = () => {
|
|
|
|
|
const app = express();
|
|
|
|
|
app.use(express.json());
|
|
|
|
|
registerTtsRoutes(app, {
|
|
|
|
|
resolveZenModel: async () => 'gpt-5-nano',
|
|
|
|
|
sayTTSCapability: null,
|
|
|
|
|
});
|
|
|
|
|
return app;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
describe('tts routes', () => {
|
2026-05-19 02:06:52 +03:00
|
|
|
it('returns local note fallback while model summarization is retired', async () => {
|
2026-04-30 13:07:37 +03:00
|
|
|
const response = await request(createApp())
|
|
|
|
|
.post('/api/text/summarize')
|
|
|
|
|
.send({
|
|
|
|
|
text: 'First sentence. Second sentence with the useful insight.',
|
|
|
|
|
threshold: 0,
|
|
|
|
|
maxLength: 100,
|
|
|
|
|
mode: 'note',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(response.status).toBe(200);
|
|
|
|
|
expect(response.body).toMatchObject({
|
2026-05-19 02:06:52 +03:00
|
|
|
summary: 'First sentence.',
|
|
|
|
|
summarized: false,
|
|
|
|
|
reason: 'Model summarization provider unavailable',
|
2026-04-30 13:07:37 +03:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-19 02:06:52 +03:00
|
|
|
it('keeps notification fallback behavior without calling zen', async () => {
|
2026-04-30 13:07:37 +03:00
|
|
|
const response = await request(createApp())
|
|
|
|
|
.post('/api/text/summarize')
|
|
|
|
|
.send({
|
|
|
|
|
text: 'Notification text that should fall back cleanly.',
|
|
|
|
|
threshold: 0,
|
|
|
|
|
maxLength: 100,
|
|
|
|
|
mode: 'notification',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(response.status).toBe(200);
|
|
|
|
|
expect(response.body).toMatchObject({
|
|
|
|
|
summary: 'Notification text that should fall back cleanly.',
|
|
|
|
|
summarized: false,
|
2026-05-19 02:06:52 +03:00
|
|
|
reason: 'Model summarization provider unavailable',
|
2026-04-30 13:07:37 +03:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-06-09 00:15:54 +08:00
|
|
|
|
|
|
|
|
describe('normalizeCustomOpenAIBaseURL', () => {
|
|
|
|
|
const originalRuntime = process.env.OPENCHAMBER_RUNTIME;
|
|
|
|
|
const originalAllowRemote = process.env.OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS;
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
// Restore env vars after each test
|
|
|
|
|
if (originalRuntime === undefined) {
|
|
|
|
|
delete process.env.OPENCHAMBER_RUNTIME;
|
|
|
|
|
} else {
|
|
|
|
|
process.env.OPENCHAMBER_RUNTIME = originalRuntime;
|
|
|
|
|
}
|
|
|
|
|
if (originalAllowRemote === undefined) {
|
|
|
|
|
delete process.env.OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS;
|
|
|
|
|
} else {
|
|
|
|
|
process.env.OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS = originalAllowRemote;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects remote URLs when OPENCHAMBER_RUNTIME is not set (web)', () => {
|
|
|
|
|
delete process.env.OPENCHAMBER_RUNTIME;
|
|
|
|
|
delete process.env.OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS;
|
|
|
|
|
|
|
|
|
|
const result = normalizeCustomOpenAIBaseURL('https://my-tts-server.example.com/v1');
|
|
|
|
|
expect(result.error).toMatch(/Remote custom server URLs are disabled/);
|
|
|
|
|
expect(result.value).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('allows remote URLs when OPENCHAMBER_RUNTIME is desktop', () => {
|
|
|
|
|
process.env.OPENCHAMBER_RUNTIME = 'desktop';
|
|
|
|
|
delete process.env.OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS;
|
|
|
|
|
|
|
|
|
|
const result = normalizeCustomOpenAIBaseURL('https://my-tts-server.example.com/v1');
|
|
|
|
|
expect(result.error).toBeUndefined();
|
|
|
|
|
expect(result.value).toBe('https://my-tts-server.example.com/v1');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('allows remote URLs when OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS is true', () => {
|
|
|
|
|
delete process.env.OPENCHAMBER_RUNTIME;
|
|
|
|
|
process.env.OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS = 'true';
|
|
|
|
|
|
|
|
|
|
const result = normalizeCustomOpenAIBaseURL('https://my-tts-server.example.com/v1');
|
|
|
|
|
expect(result.error).toBeUndefined();
|
|
|
|
|
expect(result.value).toBe('https://my-tts-server.example.com/v1');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('allows localhost URLs regardless of runtime', () => {
|
|
|
|
|
delete process.env.OPENCHAMBER_RUNTIME;
|
|
|
|
|
delete process.env.OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS;
|
|
|
|
|
|
|
|
|
|
const result = normalizeCustomOpenAIBaseURL('http://localhost:8880/v1');
|
|
|
|
|
expect(result.error).toBeUndefined();
|
|
|
|
|
expect(result.value).toBe('http://localhost:8880/v1');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('strips query strings and trailing slashes', () => {
|
|
|
|
|
process.env.OPENCHAMBER_RUNTIME = 'desktop';
|
|
|
|
|
|
|
|
|
|
const result = normalizeCustomOpenAIBaseURL('https://my-server.com/v1/?key=123');
|
|
|
|
|
expect(result.value).toBe('https://my-server.com/v1');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('denies remote URLs on desktop when env var is explicitly false', () => {
|
|
|
|
|
process.env.OPENCHAMBER_RUNTIME = 'desktop';
|
|
|
|
|
process.env.OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS = 'false';
|
|
|
|
|
|
|
|
|
|
const result = normalizeCustomOpenAIBaseURL('https://my-tts-server.example.com/v1');
|
|
|
|
|
expect(result.error).toMatch(/Remote custom server URLs are disabled/);
|
|
|
|
|
expect(result.value).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
});
|