// Tests for the marketplace version-consistency gate. // Pure classifier is the unit under test — I/O shell (runGate/inspectPlugin) is exercised // against the live tree by the CLI, not here. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { normalizeVersion, extractBadgeVersion, extractCatalogLabel, classifyPlugin, } from './check-versions.mjs'; test('normalizeVersion strips a leading v', () => { assert.equal(normalizeVersion('v5.4.0'), '5.4.0'); assert.equal(normalizeVersion('5.4.0'), '5.4.0'); assert.equal(normalizeVersion(' v1.16.0 '), '1.16.0'); }); test('extractBadgeVersion pulls the version from a shields.io badge', () => { assert.equal(extractBadgeVersion('![Version](https://img.shields.io/badge/version-5.4.0-blue)'), '5.4.0'); assert.equal(extractBadgeVersion('no badge here'), null); }); test('all-consistent plugin → OK', () => { const r = classifyPlugin({ name: 'config-audit', catalogRef: 'v5.4.0', pluginVersion: '5.4.0', readmeBadge: '5.4.0', tags: ['v5.4.0', 'v5.3.0'], }); assert.equal(r.status, 'OK'); assert.ok(!r.findings.some(f => f.level === 'ERROR' || f.level === 'WARN')); }); test('catalog ref with no matching tag → ERROR (install-breaking)', () => { const r = classifyPlugin({ name: 'voyage', catalogRef: 'v5.5.0', pluginVersion: '5.5.0', readmeBadge: '5.5.0', tags: ['v5.1.1'], }); assert.equal(r.status, 'ERROR'); assert.ok(r.findings.some(f => f.level === 'ERROR' && /no matching git tag/.test(f.msg))); }); test('plugin.json version != README badge → ERROR (internal corruption)', () => { const r = classifyPlugin({ name: 'x', catalogRef: 'v1.0.0', pluginVersion: '1.0.0', readmeBadge: '0.9.0', tags: ['v1.0.0'], }); assert.equal(r.status, 'ERROR'); assert.ok(r.findings.some(f => f.level === 'ERROR' && /README version-badge/.test(f.msg))); }); test('catalog ref behind a RELEASED version (tag exists) → WARN, suggests bumping catalog', () => { const r = classifyPlugin({ name: 'x', catalogRef: 'v1.15.0', pluginVersion: '1.16.0', readmeBadge: '1.16.0', tags: ['v1.16.0', 'v1.15.0'], }); assert.equal(r.status, 'WARN'); assert.ok(r.findings.some(f => f.level === 'WARN' && /bump catalog ref/.test(f.msg))); }); test('plugin.json ahead with no tag of its own → WARN, flags unreleased/untagged', () => { const r = classifyPlugin({ name: 'ms-ai-architect', catalogRef: 'v1.15.0', pluginVersion: '1.16.0', readmeBadge: '1.16.0', tags: ['v1.15.0'], }); assert.equal(r.status, 'WARN'); assert.ok(r.findings.some(f => f.level === 'WARN' && /never tagged|unreleased/.test(f.msg))); }); test('extractCatalogLabel reads the catalog README label by plugin name', () => { const readme = [ '### [Config-Audit](https://git.fromaitochitta.com/open/config-audit) `v5.7.0`', '### [MS AI Architect](https://git.fromaitochitta.com/open/ms-ai-architect) `v1.15.0` `🇳🇴 Norwegian`', ].join('\n'); assert.equal(extractCatalogLabel(readme, 'config-audit'), '5.7.0'); // first vX.Y.Z token wins — trailing flag/lang badge on the same line is ignored assert.equal(extractCatalogLabel(readme, 'ms-ai-architect'), '1.15.0'); // no heading for this plugin → null (check is skipped) assert.equal(extractCatalogLabel(readme, 'ghost'), null); }); test('catalog README label != catalog ref → ERROR (doc misstates installed version)', () => { const r = classifyPlugin({ name: 'config-audit', catalogRef: 'v5.7.0', pluginVersion: '5.7.0', readmeBadge: '5.7.0', tags: ['v5.7.0'], catalogLabel: '5.5.0', }); assert.equal(r.status, 'ERROR'); assert.ok(r.findings.some(f => f.level === 'ERROR' && /README label/.test(f.msg))); }); test('catalog README label == catalog ref → no label finding (stays OK)', () => { const r = classifyPlugin({ name: 'config-audit', catalogRef: 'v5.7.0', pluginVersion: '5.7.0', readmeBadge: '5.7.0', tags: ['v5.7.0'], catalogLabel: '5.7.0', }); assert.equal(r.status, 'OK'); }); test('label check does not fire on the legitimate ref-lags-plugin.json WARN case', () => { // ref v1.15.0 lags plugin.json 1.16.0 (WARN), but the label matches the ref → no extra ERROR const r = classifyPlugin({ name: 'ms-ai-architect', catalogRef: 'v1.15.0', pluginVersion: '1.16.0', readmeBadge: '1.16.0', tags: ['v1.15.0'], catalogLabel: '1.15.0', }); assert.equal(r.status, 'WARN'); assert.ok(!r.findings.some(f => f.level === 'ERROR')); }); test('catalogLabel omitted (legacy callers) → label check skipped', () => { const r = classifyPlugin({ name: 'x', catalogRef: 'v1.0.0', pluginVersion: '1.0.0', readmeBadge: '1.0.0', tags: ['v1.0.0'], }); assert.equal(r.status, 'OK'); }); test('plugin repo not found locally → SKIP', () => { const r = classifyPlugin({ name: 'gone', catalogRef: 'v1.0.0', pluginVersion: null, readmeBadge: null, tags: null, }); assert.equal(r.status, 'SKIP'); }); test('ERROR dominates WARN when both apply', () => { const r = classifyPlugin({ name: 'x', catalogRef: 'v2.0.0', pluginVersion: '2.1.0', readmeBadge: '2.1.0', tags: ['v1.9.0'], // ref v2.0.0 dangling AND catalog behind v2.1.0 }); assert.equal(r.status, 'ERROR'); });