blob: 8ca634d8133ec6d48e2deaecc45f728fb0dbefa9 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import assert from 'node:assert/strict';
import { mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { test } from 'node:test';
import { main, PROVIDERS } from './sync-model-metadata.mjs';
function fixtureCatalog() {
const catalog = {};
for (const sourceId of new Set(Object.values(PROVIDERS))) {
catalog[sourceId] = {
id: sourceId,
name: `Provider ${sourceId}`,
doc: `https://example.test/${sourceId}`,
models: {
model: {
name: 'Model',
limit: { context: 1024, output: 128 },
reasoning: false,
tool_call: true,
cost: { input: 1, output: 2 },
},
},
};
}
catalog.unused = { id: 'unused', name: 'Unused', doc: 'https://example.test/unused', models: {} };
return catalog;
}
test('a refresh persists the exact selected input and check fails on stale output', async () => {
const root = await mkdtemp(join(tmpdir(), 'maka-model-snapshot-'));
try {
const input = join(root, 'api.json');
const snapshot = join(root, 'snapshot.json');
const metadata = join(root, 'metadata.ts');
const pricing = join(root, 'pricing.ts');
await writeFile(input, JSON.stringify(fixtureCatalog()));
await main([
'node',
'sync-model-metadata.mjs',
'--refresh',
'--refresh-input',
input,
'--snapshot',
snapshot,
'--output',
metadata,
'--pricing-output',
pricing,
]);
const persisted = JSON.parse(await readFile(snapshot, 'utf8'));
assert.equal(persisted.formatVersion, 1);
assert.equal(persisted.origin.kind, 'models-dev-response');
assert.equal(persisted.projection.metadata.unused, undefined);
assert.deepEqual(
Object.keys(persisted.projection.metadata).sort(),
Object.keys(PROVIDERS).sort(),
);
assert.match(await readFile(metadata, 'utf8'), new RegExp(persisted.projectionSha256));
await main([
'node',
'sync-model-metadata.mjs',
'--check',
'--snapshot',
snapshot,
'--output',
metadata,
'--pricing-output',
pricing,
]);
const corrected = fixtureCatalog();
corrected.anthropic.models.model.name = 'Corrected Model';
corrected.anthropic.models.model.cost.input = 1.5;
await writeFile(input, JSON.stringify(corrected));
await main([
'node',
'sync-model-metadata.mjs',
'--refresh',
'--refresh-input',
input,
'--snapshot',
snapshot,
'--output',
metadata,
'--pricing-output',
pricing,
]);
const correctedProjection = JSON.parse(await readFile(snapshot, 'utf8')).projection;
assert.equal(correctedProjection.metadata.anthropic.model.displayName, 'Corrected Model');
assert.equal(
correctedProjection.pricing.find((entry) => entry.modelKey === 'anthropic:model')
.inputUsdPer1M,
1.5,
);
await writeFile(
metadata,
(await readFile(metadata, 'utf8')).replace(
"displayName: 'Corrected Model'",
"displayName: 'Stale'",
),
);
await assert.rejects(
main([
'node',
'sync-model-metadata.mjs',
'--check',
'--snapshot',
snapshot,
'--output',
metadata,
'--pricing-output',
pricing,
]),
/is stale/,
);
} finally {
await rm(root, { recursive: true, force: true });
}
});
test('a modified snapshot fails closed before generation', async () => {
const root = await mkdtemp(join(tmpdir(), 'maka-model-snapshot-tamper-'));
try {
const input = join(root, 'api.json');
const snapshot = join(root, 'snapshot.json');
const metadata = join(root, 'metadata.ts');
await writeFile(input, JSON.stringify(fixtureCatalog()));
await main([
'node',
'sync-model-metadata.mjs',
'--refresh',
'--refresh-input',
input,
'--snapshot',
snapshot,
'--output',
metadata,
]);
const persisted = JSON.parse(await readFile(snapshot, 'utf8'));
persisted.projection.metadata.anthropic.model.displayName = 'tampered';
await writeFile(snapshot, JSON.stringify(persisted));
await assert.rejects(
main(['node', 'sync-model-metadata.mjs', '--snapshot', snapshot, '--output', metadata]),
/digest mismatch/,
);
} finally {
await rm(root, { recursive: true, force: true });
}
});
test('refresh rejects an empty required provider before replacing outputs', async () => {
const root = await mkdtemp(join(tmpdir(), 'maka-model-snapshot-empty-provider-'));
try {
const input = join(root, 'api.json');
const snapshot = join(root, 'snapshot.json');
const metadata = join(root, 'metadata.ts');
const catalog = fixtureCatalog();
catalog.anthropic.models = {};
await writeFile(input, JSON.stringify(catalog));
await writeFile(snapshot, 'old snapshot');
await writeFile(metadata, 'old metadata');
await assert.rejects(
main([
'node',
'sync-model-metadata.mjs',
'--refresh',
'--refresh-input',
input,
'--snapshot',
snapshot,
'--output',
metadata,
]),
/provider anthropic has no non-empty models object/,
);
assert.equal(await readFile(snapshot, 'utf8'), 'old snapshot');
assert.equal(await readFile(metadata, 'utf8'), 'old metadata');
} finally {
await rm(root, { recursive: true, force: true });
}
});
test('refresh rejects unknown model modalities instead of dropping them', async () => {
const root = await mkdtemp(join(tmpdir(), 'maka-model-snapshot-modalities-'));
try {
const input = join(root, 'api.json');
const snapshot = join(root, 'snapshot.json');
const metadata = join(root, 'metadata.ts');
const catalog = fixtureCatalog();
catalog.anthropic.models.model.modalities = { input: ['text', 'video'], output: ['text'] };
await writeFile(input, JSON.stringify(catalog));
await assert.rejects(
main([
'node',
'sync-model-metadata.mjs',
'--refresh',
'--refresh-input',
input,
'--snapshot',
snapshot,
'--output',
metadata,
]),
/unsupported modalities/,
);
} finally {
await rm(root, { recursive: true, force: true });
}
});
test('refresh rejects a partial provider shrink until it is explicitly accepted', async () => {
const root = await mkdtemp(join(tmpdir(), 'maka-model-snapshot-provider-shrink-'));
try {
const input = join(root, 'api.json');
const snapshot = join(root, 'snapshot.json');
const metadata = join(root, 'metadata.ts');
const pricing = join(root, 'pricing.ts');
const initial = fixtureCatalog();
initial.anthropic.models['model-two'] = {
name: 'Model Two',
limit: { context: 2048, output: 256 },
reasoning: true,
tool_call: true,
cost: { input: 3, output: 4 },
};
await writeFile(input, JSON.stringify(initial));
const refreshArgs = [
'node',
'sync-model-metadata.mjs',
'--refresh',
'--refresh-input',
input,
'--snapshot',
snapshot,
'--output',
metadata,
'--pricing-output',
pricing,
];
await main(refreshArgs);
const before = {
snapshot: await readFile(snapshot, 'utf8'),
metadata: await readFile(metadata, 'utf8'),
pricing: await readFile(pricing, 'utf8'),
};
await writeFile(input, JSON.stringify(fixtureCatalog()));
await assert.rejects(main(refreshArgs), /projection paths: \/metadata\/anthropic\/model-two/);
assert.deepEqual(
{
snapshot: await readFile(snapshot, 'utf8'),
metadata: await readFile(metadata, 'utf8'),
pricing: await readFile(pricing, 'utf8'),
},
before,
);
await main([...refreshArgs, '--accept-upstream-removals']);
const accepted = JSON.parse(await readFile(snapshot, 'utf8'));
assert.equal(accepted.projection.metadata.anthropic['model-two'], undefined);
} finally {
await rm(root, { recursive: true, force: true });
}
});
test('refresh rejects lost pricing coverage from an otherwise valid model', async () => {
const root = await mkdtemp(join(tmpdir(), 'maka-model-snapshot-pricing-shrink-'));
try {
const input = join(root, 'api.json');
const snapshot = join(root, 'snapshot.json');
const metadata = join(root, 'metadata.ts');
const pricing = join(root, 'pricing.ts');
const refreshArgs = [
'node',
'sync-model-metadata.mjs',
'--refresh',
'--refresh-input',
input,
'--snapshot',
snapshot,
'--output',
metadata,
'--pricing-output',
pricing,
];
await writeFile(input, JSON.stringify(fixtureCatalog()));
await main(refreshArgs);
const before = {
snapshot: await readFile(snapshot, 'utf8'),
metadata: await readFile(metadata, 'utf8'),
pricing: await readFile(pricing, 'utf8'),
};
const truncated = fixtureCatalog();
delete truncated.anthropic.models.model.cost;
await writeFile(input, JSON.stringify(truncated));
await assert.rejects(main(refreshArgs), /projection paths: \/pricing\/anthropic:model/);
assert.deepEqual(
{
snapshot: await readFile(snapshot, 'utf8'),
metadata: await readFile(metadata, 'utf8'),
pricing: await readFile(pricing, 'utf8'),
},
before,
);
} finally {
await rm(root, { recursive: true, force: true });
}
});
test('refresh rejects nested capability and pricing shrinkage', async () => {
const root = await mkdtemp(join(tmpdir(), 'maka-model-snapshot-nested-shrink-'));
try {
const input = join(root, 'api.json');
const snapshot = join(root, 'snapshot.json');
const metadata = join(root, 'metadata.ts');
const pricing = join(root, 'pricing.ts');
const refreshArgs = [
'node',
'sync-model-metadata.mjs',
'--refresh',
'--refresh-input',
input,
'--snapshot',
snapshot,
'--output',
metadata,
'--pricing-output',
pricing,
];
const initial = fixtureCatalog();
initial.anthropic.models.model.modalities = {
input: ['text', 'image'],
output: ['text'],
};
initial.anthropic.models.model.reasoning = true;
initial.anthropic.models.model.cost.cache_read = 0.25;
await writeFile(input, JSON.stringify(initial));
await main(refreshArgs);
const before = {
snapshot: await readFile(snapshot, 'utf8'),
metadata: await readFile(metadata, 'utf8'),
pricing: await readFile(pricing, 'utf8'),
};
const truncated = structuredClone(initial);
truncated.anthropic.models.model.modalities.input = ['text'];
delete truncated.anthropic.models.model.cost.cache_read;
await writeFile(input, JSON.stringify(truncated));
await assert.rejects(
main(refreshArgs),
(error) =>
/\/metadata\/anthropic\/model\/modalities\/input value "image"/.test(error.message) &&
/\/pricing\/anthropic:model\/cacheReadUsdPer1M/.test(error.message),
);
assert.deepEqual(
{
snapshot: await readFile(snapshot, 'utf8'),
metadata: await readFile(metadata, 'utf8'),
pricing: await readFile(pricing, 'utf8'),
},
before,
);
for (const [sourceField, projectionField] of [
['reasoning', 'reasoning'],
['tool_call', 'functionCalling'],
]) {
const capabilityLoss = structuredClone(initial);
capabilityLoss.anthropic.models.model[sourceField] = false;
await writeFile(input, JSON.stringify(capabilityLoss));
await assert.rejects(
main(refreshArgs),
new RegExp(`/metadata/anthropic/model/capabilities/${projectionField}`),
);
assert.deepEqual(
{
snapshot: await readFile(snapshot, 'utf8'),
metadata: await readFile(metadata, 'utf8'),
pricing: await readFile(pricing, 'utf8'),
},
before,
);
}
const accepted = structuredClone(initial);
accepted.anthropic.models.model.tool_call = false;
await writeFile(input, JSON.stringify(accepted));
await main([...refreshArgs, '--accept-upstream-removals']);
assert.equal(
JSON.parse(await readFile(snapshot, 'utf8')).projection.metadata.anthropic.model.capabilities
.functionCalling,
false,
);
} finally {
await rm(root, { recursive: true, force: true });
}
});
test('refresh leaves all targets unchanged when any output cannot be staged', async () => {
const root = await mkdtemp(join(tmpdir(), 'maka-model-snapshot-transaction-'));
try {
const input = join(root, 'api.json');
const snapshot = join(root, 'snapshot.json');
const metadata = join(root, 'metadata.ts');
const pricing = join(root, 'pricing.ts');
const missingPricing = join(root, 'missing', 'pricing.ts');
await writeFile(input, JSON.stringify(fixtureCatalog()));
await main([
'node',
'sync-model-metadata.mjs',
'--refresh',
'--refresh-input',
input,
'--snapshot',
snapshot,
'--output',
metadata,
'--pricing-output',
pricing,
]);
const before = {
snapshot: await readFile(snapshot, 'utf8'),
metadata: await readFile(metadata, 'utf8'),
};
await assert.rejects(
main([
'node',
'sync-model-metadata.mjs',
'--refresh',
'--refresh-input',
input,
'--snapshot',
snapshot,
'--output',
metadata,
'--pricing-output',
missingPricing,
]),
/ENOENT/,
);
assert.equal(await readFile(snapshot, 'utf8'), before.snapshot);
assert.equal(await readFile(metadata, 'utf8'), before.metadata);
await assert.rejects(readFile(missingPricing), /ENOENT/);
assert.deepEqual((await readdir(root)).sort(), [
'api.json',
'metadata.ts',
'pricing.ts',
'snapshot.json',
]);
} finally {
await rm(root, { recursive: true, force: true });
}
});