Add platform adapters and tests for CLIProxyAPI, OneHub, Sub2API, and title hint detection

- Implemented `CliProxyApiAdapter` with detection logic for CLIProxyAPI.
- Added tests for `CliProxyApiAdapter` to verify detection and response handling.
- Created `OneHubAdapter` with methods for fetching models and user groups.
- Added tests for `OneHubAdapter` to ensure correct fallback and response parsing.
- Developed `Sub2ApiAdapter` with detection and user info fetching capabilities.
- Added comprehensive tests for `Sub2ApiAdapter` covering various scenarios.
- Introduced `TitleHintPlatform` type and detection logic based on HTML title.
- Implemented tests for title hint detection to identify platforms from URLs.
This commit is contained in:
ciat-777
2026-03-02 20:26:02 +08:00
parent 897273fad4
commit 487bd23a02
19 changed files with 2032 additions and 45 deletions
@@ -0,0 +1,80 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
import { AddressInfo } from 'node:net';
import { CliProxyApiAdapter } from './cliproxyapi.js';
describe('CliProxyApiAdapter detect', () => {
let server: ReturnType<typeof createServer> | undefined;
let baseUrl = '';
let adapter: CliProxyApiAdapter;
beforeEach(() => {
adapter = new CliProxyApiAdapter();
});
afterEach(async () => {
if (!server) return;
const current = server;
server = undefined;
await new Promise<void>((resolve, reject) => {
current.close((err?: Error) => (err ? reject(err) : resolve()));
});
});
async function startServer(handler: (req: IncomingMessage, res: ServerResponse) => void) {
await new Promise<void>((resolve) => {
server = createServer(handler);
server.listen(0, '127.0.0.1', () => {
const address = server!.address() as AddressInfo;
baseUrl = `http://127.0.0.1:${address.port}`;
resolve();
});
});
}
it('detects known default cliproxy port', async () => {
expect(await adapter.detect('http://127.0.0.1:8317')).toBe(true);
});
it('does not treat generic 401 response as cliproxy', async () => {
await startServer((req, res) => {
if (req.url === '/v0/management/openai-compatibility') {
res.writeHead(401, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'unauthorized' }));
return;
}
res.writeHead(404).end();
});
expect(await adapter.detect(baseUrl)).toBe(false);
});
it('detects cliproxy management endpoint by X-CPA headers', async () => {
await startServer((req, res) => {
if (req.url === '/v0/management/openai-compatibility') {
res.writeHead(401, {
'Content-Type': 'application/json',
'X-CPA-VERSION': 'v0.0.0-test',
});
res.end(JSON.stringify({ error: 'missing management key' }));
return;
}
res.writeHead(404).end();
});
expect(await adapter.detect(baseUrl)).toBe(true);
});
it('detects open management endpoint by response payload key', async () => {
await startServer((req, res) => {
if (req.url === '/v0/management/openai-compatibility') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ 'openai-compatibility': [] }));
return;
}
res.writeHead(404).end();
});
expect(await adapter.detect(baseUrl)).toBe(true);
});
});