144 lines
4.9 KiB
TypeScript
144 lines
4.9 KiB
TypeScript
import { describe, it, expect, assert } from 'vitest'
|
|
import { isJsonFile, validateOrRepairJson } from '../src/json-utils'
|
|
|
|
describe('json-utils', () => {
|
|
describe('isJsonFile', () => {
|
|
it('detects .json extension from path', () => {
|
|
expect(isJsonFile('config.json', 'config.json')).toBe(true)
|
|
expect(isJsonFile('src/data/config.json', 'config.json')).toBe(true)
|
|
expect(isJsonFile('package.json', 'package.json')).toBe(true)
|
|
})
|
|
|
|
it('detects .json extension from name when path differs', () => {
|
|
expect(isJsonFile('/some/path/file', 'file.json')).toBe(true)
|
|
})
|
|
|
|
it('rejects non-json files', () => {
|
|
expect(isJsonFile('src/hello.ts', 'hello.ts')).toBe(false)
|
|
expect(isJsonFile('readme.md', 'readme.md')).toBe(false)
|
|
expect(isJsonFile('data.jsonl', 'data.jsonl')).toBe(false)
|
|
expect(isJsonFile('config.yaml', 'config.yaml')).toBe(false)
|
|
})
|
|
|
|
it('rejects files with json in the name but not as extension', () => {
|
|
expect(isJsonFile('json-parser.ts', 'json-parser.ts')).toBe(false)
|
|
expect(isJsonFile('myjsonfile.txt', 'myjsonfile.txt')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('validateOrRepairJson', () => {
|
|
// --- valid JSON ---
|
|
|
|
it('accepts a valid JSON object', () => {
|
|
const result = validateOrRepairJson('{"name": "test", "value": 42}')
|
|
assert(result.valid)
|
|
expect(result.data).toEqual({ name: 'test', value: 42 })
|
|
expect(result.repaired).toBe(false)
|
|
})
|
|
|
|
it('accepts a valid JSON array', () => {
|
|
const result = validateOrRepairJson('[1, 2, 3]')
|
|
assert(result.valid)
|
|
expect(result.data).toEqual([1, 2, 3])
|
|
})
|
|
|
|
it('accepts JSON primitives', () => {
|
|
for (const [input, expected] of [
|
|
['"hello"', 'hello'],
|
|
['42', 42],
|
|
['true', true],
|
|
['null', null],
|
|
] as const) {
|
|
const result = validateOrRepairJson(input)
|
|
assert(result.valid)
|
|
expect(result.data).toEqual(expected)
|
|
}
|
|
})
|
|
|
|
it('accepts pretty-printed JSON', () => {
|
|
const content = JSON.stringify({ name: 'app', version: '1.0.0' }, null, 2)
|
|
const result = validateOrRepairJson(content)
|
|
assert(result.valid)
|
|
expect(result.data).toEqual({ name: 'app', version: '1.0.0' })
|
|
expect(result.repaired).toBe(false)
|
|
expect(result.content).toBe(content)
|
|
})
|
|
|
|
it('accepts complex nested JSON', () => {
|
|
const content = JSON.stringify(
|
|
{
|
|
database: { host: 'localhost', port: 5432, pool: { min: 2, max: 10 } },
|
|
features: ['auth', 'logging'],
|
|
},
|
|
null,
|
|
2
|
|
)
|
|
const result = validateOrRepairJson(content)
|
|
assert(result.valid)
|
|
expect(result.repaired).toBe(false)
|
|
})
|
|
|
|
// --- repairable JSON ---
|
|
|
|
it('repairs missing quotes on keys', () => {
|
|
const result = validateOrRepairJson('{name: "test"}')
|
|
assert(result.valid)
|
|
expect(result.repaired).toBe(true)
|
|
expect(result.data).toEqual({ name: 'test' })
|
|
})
|
|
|
|
it('repairs trailing commas', () => {
|
|
const result = validateOrRepairJson('{"a": 1, "b": 2,}')
|
|
assert(result.valid)
|
|
expect(result.repaired).toBe(true)
|
|
expect(result.data).toEqual({ a: 1, b: 2 })
|
|
})
|
|
|
|
it('repairs single quotes', () => {
|
|
const result = validateOrRepairJson("{'name': 'test'}")
|
|
assert(result.valid)
|
|
expect(result.repaired).toBe(true)
|
|
expect(result.data).toEqual({ name: 'test' })
|
|
})
|
|
|
|
it('repairs missing closing brace', () => {
|
|
const result = validateOrRepairJson('{"name": "test"')
|
|
assert(result.valid)
|
|
expect(result.repaired).toBe(true)
|
|
expect(result.data).toEqual({ name: 'test' })
|
|
})
|
|
|
|
it('repairs missing closing bracket in array', () => {
|
|
const result = validateOrRepairJson('[1, 2, 3')
|
|
assert(result.valid)
|
|
expect(result.repaired).toBe(true)
|
|
expect(result.data).toEqual([1, 2, 3])
|
|
})
|
|
|
|
it('repairs trailing comma in nested object', () => {
|
|
const result = validateOrRepairJson('{"app": {"name": "test",}, "version": "1.0.0"}')
|
|
assert(result.valid)
|
|
expect(result.repaired).toBe(true)
|
|
expect(result.data).toEqual({ app: { name: 'test' }, version: '1.0.0' })
|
|
})
|
|
|
|
// --- invalid JSON (unrepairable) → formatted error ---
|
|
|
|
it('returns a formatted error with line numbers when unrepairable', () => {
|
|
const result = validateOrRepairJson('{}{}{}')
|
|
assert(!result.valid)
|
|
expect(result.error).toContain('JSON Syntax Error:')
|
|
expect(result.error).toMatch(/\d+\|/)
|
|
})
|
|
|
|
it('returns an error marking the exact error line and column', () => {
|
|
// jsonrepair cannot fix multiple adjacent root-level braced objects without content
|
|
const content = '{\n "a": 1\n}{'
|
|
const result = validateOrRepairJson(content)
|
|
assert(!result.valid)
|
|
expect(result.error).toContain('← ERROR')
|
|
expect(result.error).toContain('^')
|
|
})
|
|
})
|
|
})
|