Ctrl K
ComunidadGuías…

vlib/json2/doc_path_coverage_test.v

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// JSON5 document and path coverage follows the API now provided by json2.
module json2

import os

struct DocCoverageSample {
	name  string
	count int
}

const sample_doc = '{a: {b: [10, 20, 30]}}'
const quoted_path = 'a."b.c"'
const quoted_doc = '{a: {"b.c": 5}}'

fn load_sample() !Doc {
	return parse_text(sample_doc, json5: true)!
}

fn test_doc_value_walks_keys_indices_and_quoted_keys() {
	d := load_sample()!
	assert d.value('a.b[0]').int() == 10
	assert d.value('a.b[2]').int() == 30
	assert d.value('a').as_array().len == 1
	// Measured: an out-of-range index and a missing key both resolve to
	// `null` rather than erroring.
	assert d.value('a.b[9]') is Null
	assert d.value('a.zz') is Null
	// A quoted key keeps the dot it contains.
	assert parse_text(quoted_doc, json5: true)!.value(quoted_path).int() == 5
}

fn test_doc_value_is_null_when_a_step_meets_the_wrong_shape() {
	d := parse_text('{a: [], b: {}}', json5: true)!
	assert d.value('a[0]') is Null
	assert d.value('b.zz') is Null
	// Walking a key into an array, and an index into an object, are both null.
	assert d.value('a.zz') is Null
	assert d.value('[0]') is Null
}

fn test_doc_value_opt_returns_the_value_when_the_path_resolves() {
	d := load_sample()!
	assert d.value_opt('a.b[1]')!.int() == 20
	// `a` is the inner object, so coercing it to an array yields one element,
	// which is that object's only member.
	assert d.value_opt('a')!.as_array().len == 1
	assert 'b' in d.value_opt('a')!.as_map()
}

fn test_doc_value_opt_reports_a_missing_key_as_a_name_error() {
	d := load_sample()!
	if value := d.value_opt('a.zz') {
		assert false, 'a missing key must error, got `${value.str()}`'
	} else {
		assert err.msg() == 'json2: no key `a.zz`'
	}
}

fn test_doc_get_separates_a_bad_path_from_a_missing_key() {
	d := load_sample()!
	// Missing and malformed paths are none; a present JSON null remains a value.
	if v := d.get('a[x]') {
		assert false, 'a malformed path must be none, got `${v.str()}`'
	}
	assert d.get('a.zz') == none
	null_doc := parse_text('{"a": null}')!
	if v := null_doc.get('a') {
		assert v is Null
	} else {
		assert false, 'a present null must resolve'
	}
	if v := d.get('a.b[0]') {
		assert v.int() == 10
	} else {
		assert false, 'a present key must resolve'
	}
}

fn test_doc_decode_reads_the_whole_document() {
	d := parse_text('{name: "bob", count: 3}', json5: true)!
	s := d.decode[DocCoverageSample]()!
	assert s.name == 'bob'
	assert s.count == 3
}

// ---------------------------------------------------------------------
// parse_path / resolve_path
// ---------------------------------------------------------------------

fn test_path_step_str_round_trips_each_form() {
	steps := parse_path('a.b[2].c')!
	assert steps.len == 4
	assert steps[0].str() == 'a'
	assert steps[1].str() == 'b'
	assert steps[2].str() == '[2]'
	assert steps[3].str() == 'c'
	// Measured: an index step carries an empty key and the parsed index,
	// which is what `is_index` reports.
	assert steps[2].key == ''
	assert steps[2].index == 2
	assert !steps[0].is_index()
	assert steps[2].is_index()
}

fn test_path_step_is_index_is_true_only_for_index_steps() {
	steps := parse_path('[0][7]')!
	assert steps.len == 2
	assert steps[0].is_index()
	assert steps[1].is_index()
	assert steps[0].index == 0
	assert steps[1].index == 7
	key_step := PathStep{
		key:   'a'
		index: -1
	}
	assert !key_step.is_index()
	// A key step is sentinel-marked with index -1, so `is_index` is a
	// comparison against that value rather than a separate flag.
	zero_key := PathStep{
		key:   'a'
		index: 0
	}
	assert zero_key.is_index()
}

fn test_parse_path_keeps_a_dot_inside_a_quoted_key() {
	steps := parse_path(quoted_path)!
	assert steps.len == 2
	assert steps[0].key == 'a'
	assert steps[1].key == 'b.c'
	assert steps[1].index == -1
	// Single quotes work the same way.
	assert parse_path("'a b'")![0].key == 'a b'
}

fn test_parse_path_of_an_empty_string_has_no_steps() {
	assert parse_path('')!.len == 0
	assert parse_path('')!.str() == '[]'
}

fn test_parse_path_rejects_a_bracket_that_is_not_an_index() {
	// Each of these is a path the walker cannot read; every one errors rather
	// than silently producing a wrong step.
	assert path_fails('a[x]')
	assert path_fails('a[]')
	assert path_fails('a[')
}

fn test_parse_path_rejects_an_unterminated_quote() {
	assert path_fails('a."b')
	assert path_fails("a.'b")
}

fn test_parse_path_rejects_text_after_a_quoted_key() {
	assert path_fails('a."b"c')
	assert path_fails("a.'b'c")
}

fn test_parse_path_rejects_a_quote_inside_a_bare_key() {
	assert path_fails('a"b')
}

fn test_resolve_path_matches_the_shape_of_each_step() {
	nested := parse(sample_doc, json5: true)!
	step := parse_path('a.b[1]')!
	assert resolve_path(nested, step).int() == 20
	// A missing key, an out-of-range index, a key walked into an array and an
	// index walked into an object are all `null`.
	assert resolve_path(nested, parse_path('a.zz')!) is Null
	assert resolve_path(nested, parse_path('a.b[9]')!) is Null
	assert resolve_path(nested, parse_path('a.b.zz')!) is Null
	assert resolve_path(nested, parse_path('[0]')!) is Null
	assert resolve_path(nested, parse_path('a.b')!).as_array().len == 3
	assert resolve_path(nested, parse_path('a.b')!).as_array()[2].int() == 30
}

fn test_resolve_path_of_no_steps_returns_the_document_itself() {
	nested := parse(sample_doc, json5: true)!
	whole := resolve_path(nested, [])
	assert whole is map[string]Any
	assert 'a' in whole.as_map()
	// And a path with no steps applied to an array is the array itself.
	assert resolve_path(parse('[1]')!, []).as_array().len == 1
}

fn test_parse_file_reads_a_json5_document_from_disk() {
	path := os.join_path(os.vtmp_dir(), 'json5_doc_path_coverage_${os.getpid()}.json5')
	os.write_file(path, '{name: "file", count: 12}\n')!
	defer { os.rm(path) or {} }
	d := parse_file(path, json5: true)!
	assert d.value('name').str() == 'file'
	assert d.value('count').int() == 12
}

fn test_decode_any_converts_an_already_parsed_tree() {
	value := parse('{name: "bob", count: 3}', json5: true)!
	s := decode_any[DocCoverageSample](value, json5: true)!
	assert s.name == 'bob'
	assert s.count == 3
}

fn path_fails(path string) bool {
	if steps := parse_path(path) {
		assert false, '`${path}` must not parse, got ${steps.len} steps'
	}
	return true
}