import * as assert from 'assert'; import * as vscode from 'vscode'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import { buildFinalYaml, ImportService, parseImportableYaml } from '../src/rules/import-service'; import type { PreviewDecision, ImportableRule } from '../src/rules/import-types'; function rule(overrides: Partial): ImportableRule { return { id: 'r', severity: 'warning', description: 'd', message: 'm', ...overrides }; } suite('ImportService Tests', () => { test('parseImportableYaml handles arrays and quoted ids', () => { const yaml = [ '- id: no-console', ' severity: warning', ' description: forbid console', ' message: use logger', ' languages: [javascript, typescript]', '- id: "quoted-id"', ' severity: info', ' description: d', ' message: m', ].join('\n'); const rules = parseImportableYaml(yaml); assert.strictEqual(rules.length, 2); assert.deepStrictEqual(rules[0].languages, ['javascript', 'typescript']); assert.strictEqual(rules[1].id, 'quoted-id'); }); test('parseImportableYaml fills message from description and vice versa', () => { const rules = parseImportableYaml('- id: a\n severity: error\n description: only-desc\n'); assert.strictEqual(rules[0].message, 'only-desc'); const rules2 = parseImportableYaml('- id: b\n severity: error\n message: only-msg\n'); assert.strictEqual(rules2[0].description, 'only-msg'); }); test('buildFinalYaml uses edited rules when present', () => { const rules = [rule({ id: 'r1' })]; const edited = [rule({ id: 'r1', severity: 'error', description: 'edited' })]; const decision: PreviewDecision = { keepRule: { r1: true }, confirmed: true, editedRules: edited }; const out = buildFinalYaml('- id: r1\n severity: warning\n description: d\n message: m\n', rules, decision); assert.match(out, /severity: error/); assert.match(out, /edited/); }); test('buildFinalYaml comments exact duplicates and strips duplicate metadata', () => { const yaml = [ '- id: a', ' severity: warning', ' description: desc a', ' message: msg a', '- id: b', ' severity: error', ' description: desc b', ' message: msg b', ' duplicateOf: custom/x', ' duplicateLevel: exact', ' duplicateReason: same', ].join('\n'); const rules = parseImportableYaml(yaml); const decision: PreviewDecision = { keepRule: { a: true, b: false }, confirmed: true }; const out = buildFinalYaml(yaml, rules, decision); assert.match(out, /- id: a/); assert.match(out, /# - id: b/); const dupLines = out.split('\n').filter(l => /duplicateOf:/.test(l)); assert.ok(dupLines.length > 0); assert.ok(dupLines.every(l => l.trimStart().startsWith('#')), 'duplicate metadata only appears in comments'); }); test('buildFinalYaml keeps all when no overrides', () => { const yaml = '- id: x\n severity: warning\n description: dx\n message: mx\n'; const rules = parseImportableYaml(yaml); const decision: PreviewDecision = { keepRule: {}, confirmed: true }; const out = buildFinalYaml(yaml, rules, decision); assert.match(out, /- id: x/); assert.ok(!/# - id: x/.test(out)); }); test('applyConversion writes final yaml to nested target path', () => { const yaml = '- id: k\n severity: warning\n description: dk\n message: mk\n'; const rules = parseImportableYaml(yaml); const decision: PreviewDecision = { keepRule: { k: true }, confirmed: true }; const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'import-svc-')); const target = path.join(tmp, 'nested', 'custom-rules.yaml'); try { new ImportService().applyConversion( { rules, yamlContent: yaml, sourceFileName: 'a.yaml', exactCount: 0, overlapCount: 0 }, decision, target, ); assert.ok(fs.existsSync(target)); assert.match(fs.readFileSync(target, 'utf-8'), /- id: k/); } finally { fs.rmSync(tmp, { recursive: true, force: true }); } }); test('convert throws for unsupported extension', async () => { const svc = new ImportService(); await assert.rejects( () => svc.convert('/tmp/foo.xyz', {} as vscode.ExtensionContext), ); }); });