Files
miaojingAI/scripts/test-yuanjie-pricing-sync.mjs
2026-05-20 22:25:22 +08:00

167 lines
6.0 KiB
JavaScript

import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
const repoRoot = path.resolve(import.meta.dirname, '..');
const {
YUANJIE_PROVIDER_NAME,
} = await import('../src/lib/yuanjie-image-model-templates.ts');
const {
getYuanjiePricingSyncTargets,
mergeYuanjiePricingNote,
syncYuanjiePricingMetadata,
} = await import('../src/lib/yuanjie-pricing-sync.ts');
async function runTest(name, fn) {
try {
await fn();
console.log(`PASS ${name}`);
} catch (error) {
console.error(`FAIL ${name}`);
console.error(error);
process.exitCode = 1;
}
}
function read(relativePath) {
return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
}
function createFakeClient(rows) {
const calls = [];
const client = {
calls,
async query(sql, params = []) {
const text = String(sql);
calls.push({ sql: text, params });
if (text.includes('FROM system_api_configs')) {
if (text.includes('provider = $1')) {
const provider = params[0];
const types = Array.isArray(params[1]) ? params[1] : [];
return {
rows: rows.filter(row => row.provider === provider && types.includes(row.type)),
};
}
return { rows };
}
if (text.includes('UPDATE system_api_configs')) {
return { rows: [{ id: params.at(-2) || params.at(-1) }], rowCount: 1 };
}
return { rows: [], rowCount: 0 };
},
};
return client;
}
await runTest('builds Yuanjie pricing sync targets from built-in image and video templates', () => {
const targets = getYuanjiePricingSyncTargets();
assert.ok(targets.length >= 40, 'expected image and video templates to be represented');
const gptImage2 = targets.find(item => item.modelName === 'gpt-image-2');
assert.ok(gptImage2, 'missing GPT Image 2 pricing target');
assert.equal(gptImage2.type, 'image');
assert.equal(gptImage2.billingMode, 'fixed');
assert.match(gptImage2.priceNote, /元界计费同步/);
assert.match(gptImage2.priceNote, /cost/);
const seedanceToken = targets.find(item => item.modelName === 'kwvideo-v2-ref');
assert.ok(seedanceToken, 'missing Seedance token pricing target');
assert.equal(seedanceToken.billingMode, 'token');
assert.match(seedanceToken.priceNote, /Token/);
const happyhorseDuration = targets.find(item => item.modelName === 'happyhorse-t2v');
assert.ok(happyhorseDuration, 'missing HappyHorse duration pricing target');
assert.equal(happyhorseDuration.billingMode, 'duration');
assert.match(happyhorseDuration.priceNote, /按秒/);
});
await runTest('merges Yuanjie pricing note without deleting admin custom note', () => {
const target = getYuanjiePricingSyncTargets().find(item => item.modelName === 'gpt-image-2');
assert.ok(target);
const merged = mergeYuanjiePricingNote('管理员自定义:高峰期加价', target);
assert.match(merged, /管理员自定义/);
assert.match(merged, /元界计费同步/);
const replaced = mergeYuanjiePricingNote(merged, target);
assert.equal((replaced.match(/元界计费同步/g) || []).length, 1);
});
await runTest('sync updates only Yuanjie system API rows and leaves mozheAPI untouched', async () => {
const client = createFakeClient([
{
id: '11111111-1111-1111-1111-111111111111',
provider: YUANJIE_PROVIDER_NAME,
model_name: 'gpt-image-2',
type: 'image',
price_note: '管理员自定义:保留',
fixed_price: '12',
credits_per_use: 12,
},
{
id: '22222222-2222-2222-2222-222222222222',
provider: 'mozheAPI',
model_name: 'gpt-image-2',
type: 'image',
price_note: 'mozhe should not change',
fixed_price: '99',
credits_per_use: 99,
},
]);
const result = await syncYuanjiePricingMetadata(client);
assert.equal(result.updated, 1);
assert.equal(result.skipped, 0);
assert.equal(result.unmatched.length, 0);
const selectCall = client.calls.find(call => call.sql.includes('FROM system_api_configs'));
assert.ok(selectCall, 'expected a system_api_configs select');
assert.match(selectCall.sql, /type\s*=\s*ANY\(\$1::text\[\]\)/);
assert.match(selectCall.sql, /replace\(lower\(provider\)/);
assert.match(selectCall.sql, /model_group/);
assert.deepEqual(selectCall.params[0], ['image', 'video']);
assert.ok(Array.isArray(selectCall.params[1]));
assert.ok(selectCall.params[1].includes(String(YUANJIE_PROVIDER_NAME).replace(/\s+/g, '').toLowerCase()));
const updateCalls = client.calls.filter(call => call.sql.includes('UPDATE system_api_configs'));
assert.equal(updateCalls.length, 1);
assert.match(updateCalls[0].sql, /replace\(lower\(provider\)/);
assert.match(updateCalls[0].sql, /model_group/);
assert.equal(updateCalls[0].params.includes('22222222-2222-2222-2222-222222222222'), false);
});
await runTest('sync matches Yuanjie provider name variants used by existing image configs', async () => {
const client = createFakeClient([
{
id: '33333333-3333-3333-3333-333333333333',
provider: '元界AI',
model_group: 'default',
model_name: 'gpt-image-2',
type: 'image',
price_note: '',
},
]);
const result = await syncYuanjiePricingMetadata(client, { type: 'image' });
assert.equal(result.matched, 1);
assert.equal(result.updated, 1);
assert.deepEqual(result.unmatched, []);
});
await runTest('admin page exposes a manual Yuanjie pricing sync button', () => {
const source = read('src/components/admin/api-management-tab.tsx');
assert.match(source, /syncYuanjiePricing/);
assert.match(source, /\/api\/admin\/system-apis\/yuanjie-pricing/);
assert.match(source, /同步元界价格/);
});
await runTest('admin route is documented and registered separately from generic smart import', () => {
const apiReference = read('docs/codex-miaojing/api-reference.md');
const customIntegrations = read('docs/codex-miaojing/custom-integrations.md');
assert.match(apiReference, /\/api\/admin\/system-apis\/yuanjie-pricing/);
assert.match(customIntegrations, /元界价格/);
});
if (process.exitCode) process.exit(process.exitCode);