| /* |
| * 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, stat, utimes, writeFile } from 'node:fs/promises'; |
| import { tmpdir } from 'node:os'; |
| import { join } from 'node:path'; |
| import { test } from 'node:test'; |
| import { loadTypeScriptModule, 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('the committed snapshot generates both TypeScript modules offline', async () => { |
| const root = await mkdtemp(join(tmpdir(), 'maka-model-build-')); |
| try { |
| const metadata = join(root, 'metadata.ts'); |
| const pricing = join(root, 'pricing.ts'); |
| const originalFetch = globalThis.fetch; |
| globalThis.fetch = async () => assert.fail('offline generation must not fetch'); |
| try { |
| await main([ |
| 'node', |
| 'sync-model-metadata.mjs', |
| '--output', |
| metadata, |
| '--pricing-output', |
| pricing, |
| ]); |
| } finally { |
| globalThis.fetch = originalFetch; |
| } |
| const [metadataSource, pricingSource] = await Promise.all([ |
| readFile(metadata, 'utf8'), |
| readFile(pricing, 'utf8'), |
| ]); |
| const [metadataModule, pricingModule] = await Promise.all([ |
| loadTypeScriptModule(metadataSource), |
| loadTypeScriptModule(pricingSource), |
| ]); |
| |
| assert.ok(Object.keys(metadataModule.GENERATED_MODELS_DEV_METADATA).length > 0); |
| assert.ok(Array.isArray(pricingModule.GENERATED_MODEL_PRICING)); |
| assert.ok(pricingModule.GENERATED_MODEL_PRICING.length > 0); |
| } finally { |
| await rm(root, { recursive: true, force: true }); |
| } |
| }); |
| |
| test('a no-op sync preserves generated output mtimes', async () => { |
| const root = await mkdtemp(join(tmpdir(), 'maka-model-noop-')); |
| try { |
| const metadata = join(root, 'metadata.ts'); |
| const pricing = join(root, 'pricing.ts'); |
| const argv = [ |
| 'node', |
| 'sync-model-metadata.mjs', |
| '--output', |
| metadata, |
| '--pricing-output', |
| pricing, |
| ]; |
| await main(argv); |
| |
| const oldTime = new Date('2001-01-01T00:00:00.000Z'); |
| await Promise.all([utimes(metadata, oldTime, oldTime), utimes(pricing, oldTime, oldTime)]); |
| const before = await Promise.all([stat(metadata), stat(pricing)]); |
| |
| await main(argv); |
| |
| const after = await Promise.all([stat(metadata), stat(pricing)]); |
| assert.deepEqual( |
| after.map(({ mtimeMs }) => mtimeMs), |
| before.map(({ mtimeMs }) => mtimeMs), |
| ); |
| } finally { |
| await rm(root, { recursive: true, force: true }); |
| } |
| }); |
| |
| 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', 'hologram'], 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 }); |
| } |
| }); |
| |
| // The drift report goes to stdout for the scheduled job's summary; tests read |
| // the returned buckets instead of the printed text. |
| async function drift(argv) { |
| const written = []; |
| const original = process.stdout.write; |
| process.stdout.write = (chunk) => { |
| written.push(String(chunk)); |
| return true; |
| }; |
| try { |
| return { report: await main(['node', 'sync-model-metadata.mjs', '--drift', ...argv]) }; |
| } finally { |
| process.stdout.write = original; |
| assert.ok(written.join('').length > 0, 'drift must print a report'); |
| } |
| } |
| |
| test('drift finds nothing when the snapshot still matches upstream', async () => { |
| const root = await mkdtemp(join(tmpdir(), 'maka-model-drift-clean-')); |
| try { |
| const input = join(root, 'api.json'); |
| const snapshot = join(root, 'snapshot.json'); |
| await writeFile(input, JSON.stringify(fixtureCatalog())); |
| await main([ |
| 'node', |
| 'sync-model-metadata.mjs', |
| '--refresh', |
| '--refresh-input', |
| input, |
| '--snapshot', |
| snapshot, |
| '--output', |
| join(root, 'metadata.ts'), |
| ]); |
| |
| const { report } = await drift(['--snapshot', snapshot, '--refresh-input', input]); |
| assert.equal(report.drifted, false); |
| assert.deepEqual( |
| [ |
| report.added, |
| report.removed, |
| report.changed, |
| report.rejectedModels, |
| report.rejectedProviders, |
| ], |
| [[], [], [], [], []], |
| ); |
| } finally { |
| await rm(root, { recursive: true, force: true }); |
| } |
| }); |
| |
| test('drift separates a rejected shape from a real upstream difference', async () => { |
| const root = await mkdtemp(join(tmpdir(), 'maka-model-drift-')); |
| try { |
| const committed = join(root, 'api.json'); |
| const upstream = join(root, 'upstream.json'); |
| const snapshot = join(root, 'snapshot.json'); |
| const before = fixtureCatalog(); |
| before.anthropic.models.legacy = { ...before.anthropic.models.model, name: 'Legacy' }; |
| await writeFile(committed, JSON.stringify(before)); |
| await main([ |
| 'node', |
| 'sync-model-metadata.mjs', |
| '--refresh', |
| '--refresh-input', |
| committed, |
| '--snapshot', |
| snapshot, |
| '--output', |
| join(root, 'metadata.ts'), |
| ]); |
| |
| const after = JSON.parse(JSON.stringify(before)); |
| delete after.anthropic.models.legacy; |
| after.anthropic.models.added = { ...after.anthropic.models.model, name: 'Added' }; |
| after.anthropic.models.model.name = 'Renamed'; |
| after.anthropic.models.model.cost = { input: 9, output: 9 }; |
| after.groq.models.model.modalities = { input: ['text', 'hologram'], output: ['text'] }; |
| delete after.openai; |
| await writeFile(upstream, JSON.stringify(after)); |
| |
| const { report } = await drift(['--snapshot', snapshot, '--refresh-input', upstream]); |
| assert.equal(report.drifted, true); |
| assert.deepEqual(report.added, ['anthropic/added']); |
| assert.deepEqual(report.removed, ['anthropic/legacy']); |
| assert.deepEqual(report.changed, [ |
| 'anthropic/model: metadata.displayName, pricing.inputUsdPer1M, pricing.outputUsdPer1M', |
| ]); |
| assert.equal(report.rejectedProviders.length, 1); |
| assert.match(report.rejectedProviders[0], /^openai: .*provider openai is missing/u); |
| assert.equal(report.rejectedModels.length, 1); |
| assert.match(report.rejectedModels[0], /^groq\/model: .*unsupported modalities/u); |
| } finally { |
| await rm(root, { recursive: true, force: true }); |
| } |
| }); |
| |
| test('drift reports the projection sections that carry no model metadata', async () => { |
| const root = await mkdtemp(join(tmpdir(), 'maka-model-drift-sections-')); |
| try { |
| const committed = join(root, 'api.json'); |
| const upstream = join(root, 'upstream.json'); |
| const snapshot = join(root, 'snapshot.json'); |
| const before = fixtureCatalog(); |
| before.anthropic.models.model.provider = { npm: '@ai-sdk/anthropic' }; |
| await writeFile(committed, JSON.stringify(before)); |
| await main([ |
| 'node', |
| 'sync-model-metadata.mjs', |
| '--refresh', |
| '--refresh-input', |
| committed, |
| '--snapshot', |
| snapshot, |
| '--output', |
| join(root, 'metadata.ts'), |
| ]); |
| |
| // Neither a renamed provider nor a swapped npm package touches a model's |
| // metadata or its pricing, which is all the report used to compare. |
| const after = JSON.parse(JSON.stringify(before)); |
| after.anthropic.name = 'Anthropic Renamed'; |
| after.anthropic.models.model.provider = { npm: '@ai-sdk/google' }; |
| await writeFile(upstream, JSON.stringify(after)); |
| |
| const { report } = await drift(['--snapshot', snapshot, '--refresh-input', upstream]); |
| assert.equal(report.drifted, true); |
| assert.deepEqual(report.changed, [ |
| 'anthropic: providerFacts.name', |
| 'anthropic/model: providerOverrides.npm', |
| ]); |
| } finally { |
| await rm(root, { recursive: true, force: true }); |
| } |
| }); |
| |
| test('drift refuses to combine with the modes that write', async () => { |
| for (const mode of ['--refresh', '--check']) { |
| await assert.rejects( |
| main(['node', 'sync-model-metadata.mjs', '--drift', mode]), |
| /--drift reports without writing/u, |
| ); |
| } |
| }); |
| |
| test('refresh carries the video and pdf modalities models.dev declares', async () => { |
| const root = await mkdtemp(join(tmpdir(), 'maka-model-snapshot-video-')); |
| try { |
| const input = join(root, 'api.json'); |
| const snapshot = join(root, 'snapshot.json'); |
| const metadata = join(root, 'metadata.ts'); |
| const catalog = fixtureCatalog(); |
| const modalities = { input: ['text', 'video'], output: ['text', 'pdf', 'video'] }; |
| catalog.anthropic.models.model.modalities = modalities; |
| await writeFile(input, JSON.stringify(catalog)); |
| |
| await main([ |
| 'node', |
| 'sync-model-metadata.mjs', |
| '--refresh', |
| '--refresh-input', |
| input, |
| '--snapshot', |
| snapshot, |
| '--output', |
| metadata, |
| ]); |
| |
| const written = JSON.parse(await readFile(snapshot, 'utf8')); |
| assert.deepEqual(written.projection.metadata.anthropic.model.modalities, 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 }); |
| } |
| }); |