Files
herolib/lib/baobab/generator/write_object_tests.v
2025-01-02 01:42:39 -05:00

170 lines
5.1 KiB
V

module generator
import freeflowuniverse.herolib.core.code { VFile, CustomCode, Function, Import, Struct }
import freeflowuniverse.herolib.baobab.specification {BaseObject}
import rand
import freeflowuniverse.herolib.core.texttools
import os
// generate_object_methods generates CRUD actor methods for a provided structure
pub fn generate_object_test_code(actor Struct, object BaseObject) !VFile {
consts := CustomCode{"const db_dir = '\${os.home_dir()}/hero/db'
const actor_name = '${actor.name}_test_actor'"}
clean_code := 'mut actor := get(name: actor_name)!\nactor.backend.reset()!'
testsuite_begin := Function{
name: 'testsuite_begin'
body: clean_code
}
testsuite_end := Function{
name: 'testsuite_end'
body: clean_code
}
actor_name := texttools.name_fix(actor.name)
object_name := texttools.name_fix_pascal_to_snake(object.structure.name)
object_type := object.structure.name
// TODO: support modules outside of crystal
mut file := VFile{
name: '${object_name}_test'
mod: texttools.name_fix(actor_name)
imports: [
Import{
mod: 'os'
},
Import{
mod: '${object.structure.mod}'
types: [object_type]
},
]
items: [
consts,
testsuite_begin,
testsuite_end,
generate_new_method_test(actor, object)!,
generate_get_method_test(actor, object)!,
]
}
if object.structure.fields.any(it.attrs.any(it.name == 'index')) {
// can't filter without indices
file.items << generate_filter_test(actor, object)!
}
return file
}
// generate_object_methods generates CRUD actor methods for a provided structure
fn generate_new_method_test(actor Struct, object BaseObject) !Function {
object_name := texttools.name_fix_pascal_to_snake(object.structure.name)
object_type := object.structure.name
required_fields := object.structure.fields.filter(it.attrs.any(it.name == 'required'))
mut fields := []string{}
for field in required_fields {
mut field_decl := '${field.name}: ${get_mock_value(field.typ.symbol)!}'
fields << field_decl
}
body := 'mut actor := get(name: actor_name)!
mut ${object_name}_id := actor.new_${object_name}(${object_type}{${fields.join(',')}})!
assert ${object_name}_id == 1
${object_name}_id = actor.new_${object_name}(${object_type}{${fields.join(',')}})!
assert ${object_name}_id == 2'
return Function{
name: 'test_new_${object_name}'
description: 'news the ${object_type} with the given object id'
result: code.Param{
is_result: true
}
body: body
}
}
// generate_object_methods generates CRUD actor methods for a provided structure
fn generate_get_method_test(actor Struct, object BaseObject) !Function {
object_name := texttools.name_fix_pascal_to_snake(object.structure.name)
object_type := object.structure.name
required_fields := object.structure.fields.filter(it.attrs.any(it.name == 'required'))
mut fields := []string{}
for field in required_fields {
mut field_decl := '${field.name}: ${get_mock_value(field.typ.symbol)!}'
fields << field_decl
}
body := 'mut actor := get(name: actor_name)!
mut ${object_name} := ${object_type}{${fields.join(',')}}
${object_name}.id = actor.new_${object_name}(${object_name})!
assert ${object_name} == actor.get_${object_name}(${object_name}.id)!'
return Function{
name: 'test_get_${object_name}'
description: 'news the ${object_type} with the given object id'
result: code.Param{
is_result: true
}
body: body
}
}
// generate_object_methods generates CRUD actor methods for a provided structure
fn generate_filter_test(actor Struct, object BaseObject) !Function {
object_name := texttools.name_fix_pascal_to_snake(object.structure.name)
object_type := object.structure.name
index_fields := object.structure.fields.filter(it.attrs.any(it.name == 'index'))
if index_fields.len == 0 {
return error('Cannot generate filter method test for object without any index fields')
}
mut index_tests := []string{}
for i, field in index_fields {
val := get_mock_value(field.typ.symbol)!
index_field := '${field.name}: ${val}' // index field assignment line
mut fields := [index_field]
fields << get_required_fields(object.structure)!
index_tests << '${object_name}_id${i} := actor.new_${object_name}(${object_type}{${fields.join(',')}})!
${object_name}_list${i} := actor.filter_${object_name}(
filter: ${object_type}Filter{${index_field}}
)!
assert ${object_name}_list${i}.len == 1
assert ${object_name}_list${i}[0].${field.name} == ${val}
'
}
body := 'mut actor := get(name: actor_name)!
\n${index_tests.join('\n\n')}'
return Function{
name: 'test_filter_${object_name}'
description: 'news the ${object_type} with the given object id'
result: code.Param{
is_result: true
}
body: body
}
}
fn get_required_fields(s Struct) ![]string {
required_fields := s.fields.filter(it.attrs.any(it.name == 'required'))
mut fields := []string{}
for field in required_fields {
fields << '${field.name}: ${get_mock_value(field.typ.symbol)!}'
}
return fields
}
fn get_mock_value(typ string) !string {
if typ == 'string' {
return "'mock_string_${rand.string(3)}'"
} else if typ == 'int' || typ == 'u32' {
return '42'
} else {
return error('mock values for types other than strings and numbers are not yet supported')
}
}