Skip to content
This repository has been archived by the owner on Jan 23, 2024. It is now read-only.

Fi 898 generic generator from capability statement #477

Merged
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7e4c985
saving some progress
Jul 29, 2020
c751a7f
more progress
Jul 30, 2020
f1806c2
add interaction test
Jul 30, 2020
cfdf984
fix some interaction tests
plump-pinniped Aug 4, 2020
4621846
some fixes
plump-pinniped Aug 4, 2020
768698a
more fixes
plump-pinniped Aug 4, 2020
66223a3
get correct type for parameters
plump-pinniped Aug 4, 2020
87a3e75
add requirements to module file for generic ui
plump-pinniped Aug 4, 2020
2b19c8e
nil safety for multiple_and_expecation and multiple_or_expectation
radamson Aug 5, 2020
d487170
break out search combos and add some nil safety
radamson Aug 5, 2020
7a6efbb
move validation tests after read and search tests
radamson Aug 5, 2020
636fc73
Update generator/generic/generic_generator.rb
radamson Aug 5, 2020
467b335
rubocop and fix spacing
radamson Aug 5, 2020
e421cda
use capabilities attr_accessor instead of passing it around
radamson Aug 5, 2020
e045686
fix search combo change and change definitions module
plump-pinniped Aug 5, 2020
f8221c0
rename generic_utilities to sequence_utilities
radamson Aug 5, 2020
755a88c
use map in basic_searches_from_capability_statement
radamson Aug 5, 2020
31020e7
catch json parsing error
Aug 6, 2020
e9d87d4
update generator to test against correct profile
radamson Aug 6, 2020
84fab52
save resources with profile reference
radamson Aug 6, 2020
40c9969
us path when making sequence class name
plump-pinniped Aug 6, 2020
e946a81
rename ProfileDefinitions to include module path
radamson Aug 7, 2020
ff99f47
Definitions -> ProfileDefinitions to match what previously existed
radamson Aug 7, 2020
f3240fc
fix indentation
radamson Aug 7, 2020
1c4f538
add logger line for json parsing error and fix profiledefinition modu…
Aug 7, 2020
467895f
fix rubocop spacing issue
Aug 7, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 7 additions & 1 deletion generator/generator_base.rb
Expand Up @@ -27,7 +27,13 @@ def load_resources

# We should consider using the native Ruby models instead of JSON
# There were problems with round-tripping certain SearchParameters though
new_resource_json = JSON.parse(File.read(resource))

begin
new_resource_json = JSON.parse(File.read(resource))
rescue JSON::ParserError # failed to parse json
next
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably log something here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

end

new_resource = FHIR.from_contents(File.read(resource))
next unless new_resource.present?

Expand Down
54 changes: 53 additions & 1 deletion generator/generic/generic_generator.rb
Expand Up @@ -2,32 +2,46 @@

require_relative '../generator_base'
require_relative '../sequence_metadata'
require_relative '../search_parameter_metadata'
require_relative './read_test'
require_relative './profile_validation_test'
require_relative './search_test'
require_relative './interaction_test'
require_relative '../generic_generator_utilities'

module Inferno
module Generator
class GenericGenerator < Generator::Base
include ReadTest
include ProfileValidationTest
include SearchTest
include InteractionTest
include Inferno::Generator::GenericGeneratorUtilties

def resource_profiles
resources_by_type['StructureDefinition'].reject { |definition| definition['type'] == 'Extension' }
end

def sequence_metadata
@sequence_metadata ||= resource_profiles.map { |profile| SequenceMetadata.new(profile) }
@sequence_metadata ||= resource_profiles.map { |profile| SequenceMetadata.new(profile, @path, search_parameter_metadata, capability_statement) }
end

def search_parameter_metadata
@search_parameter_metadata ||= resources_by_type['SearchParameter'].map { |parameter_json| SearchParameterMetadata.new(parameter_json) }
end

def generate
generate_sequences
copy_static_files
generate_module
end

def generate_sequences
sequence_metadata.each do |metadata|
create_read_test(metadata)
create_search_tests(metadata)
create_profile_validation_test(metadata)
create_interaction_tests(metadata)
generate_sequence(metadata)
end
end
Expand All @@ -39,6 +53,38 @@ def generate_sequence(metadata)
output = template.result_with_hash(metadata: metadata)
FileUtils.mkdir_p(sequence_out_path + '/') unless File.directory?(sequence_out_path + '/')
File.write(file_name, output)

generate_sequence_definitions(metadata)
end

def generate_sequence_definitions(metadata)
output_directory = File.join(sequence_out_path, 'profile_definitions')
file_name = File.join(output_directory, metadata.file_name + '_definitions.rb')
template = ERB.new(File.read(File.join(__dir__, 'templates/sequence_definition.rb.erb')))
output = template.result_with_hash(sequence_definition_hash(metadata))
FileUtils.mkdir_p(sequence_out_path + '/profile_definitions/') unless File.directory?(sequence_out_path + '/profile_definitions/')
File.write(file_name, output)
end

def sequence_definition_hash(metadata)
search_parameters = metadata.search_parameter_metadata&.map do |param_metadata|
{
url: param_metadata.url,
code: param_metadata.code,
expression: param_metadata.expression,
multipleOr: param_metadata.multiple_or,
multipleOrExpectation: param_metadata.multiple_or_expectation,
multipleAnd: param_metadata.multiple_and,
multipleAndExpectation: param_metadata.multiple_and_expectation,
modifiers: param_metadata.modifiers,
comparators: param_metadata.comparators
}
end
search_parameters ||= []
{
class_name: metadata.class_name + 'Definition',
search_parameters: structure_to_string(search_parameters)
}
end

def module_file_path
Expand All @@ -59,6 +105,12 @@ def generate_module

File.write(file_name, output)
end

def copy_static_files
Dir.glob(File.join(__dir__, 'static', '*')).each do |static_file|
FileUtils.cp(static_file, sequence_out_path)
end
end
end
end
end
37 changes: 37 additions & 0 deletions generator/generic/interaction_test.rb
@@ -0,0 +1,37 @@
# frozen_string_literal: true

require_relative '../test_metadata'

module Inferno
module Generator
module InteractionTest
def create_interaction_tests(metadata)
metadata.interactions.each do |interaction|
next if ['read', 'search-type'].include? interaction[:code] # already have tests for
next if ['create', 'update', 'patch', 'delete', 'history-type'].include? interaction[:code] # not currently supported

interaction[:code] = 'history' if interaction[:code] == 'history-instance' # how the history interaction is called already

interaction_test = TestMetadata.new(
title: "Server supports the #{metadata.resource_type} #{interaction[:code]} interaction",
key: "resource_#{interaction[:code].gsub('-', '_').downcase}".to_sym,
description: "This test will verify that #{metadata.resource_type} #{interaction[:code]} interactions are supported by the server.",
optional: interaction[:expectation] != 'SHALL'
)

validate_reply_args = [
'@resource_found',
"versioned_resource_class('#{metadata.resource_type}')"
]
validate_reply_args_string = validate_reply_args.join(', ')

interaction_test.code = %(
skip 'No resource found from Read test' unless @resource_found.present?
validate_#{interaction[:code].gsub('-', '_')}_reply(#{validate_reply_args_string})
)
metadata.add_test(interaction_test)
end
end
end
end
end
2 changes: 1 addition & 1 deletion generator/generic/profile_validation_test.rb
Expand Up @@ -13,7 +13,7 @@ def create_profile_validation_test(metadata)
)
profile_validation_test.code = %(
skip 'No resource found from Read test' unless @resource_found.present?
test_resource_against_profile('#{metadata.resource_type}', @resource_found, '#{metadata.url}')
test_resources_against_profile('#{metadata.resource_type}','#{metadata.url}')
)
metadata.add_test(profile_validation_test)
end
Expand Down
1 change: 1 addition & 0 deletions generator/generic/read_test.rb
Expand Up @@ -14,6 +14,7 @@ def create_read_test(metadata)
read_test.code = %(
resource_id = @instance.#{metadata.resource_type.underscore}_id
@resource_found = validate_read_reply(FHIR::#{metadata.resource_type}.new(id: resource_id), FHIR::#{metadata.resource_type})
save_resource_references(versioned_resource_class('#{metadata.resource_type}'), [@resource_found], '#{metadata.url}')
)
metadata.add_test(read_test)
end
Expand Down
57 changes: 57 additions & 0 deletions generator/generic/search_test.rb
@@ -0,0 +1,57 @@
# frozen_string_literal: true

require_relative '../test_metadata'

module Inferno
module Generator
module SearchTest
def create_search_tests(metadata)
metadata.searches.each do |search|
search_test = TestMetadata.new(
title: "Server returns expected results from #{metadata.resource_type} search by #{search[:parameters].join('+')}",
key: :"search_by_#{search[:parameters].map(&:underscore).join('_')}",
description: "This test will verify that #{metadata.resource_type} resources can be searched from the server.",
optional: search[:expectation] != 'SHALL'
)

search_parameter_assignments = search[:parameters].map do |parameter|
param_metadata = metadata.search_parameter_metadata.find { |parameter_metadata| parameter_metadata.code == parameter }
path = param_metadata
.expression
.gsub(/(?<!\w)class(?!\w)/, 'local_class')
.split('.')
.drop(1)
.join('.')

# handle some fhir path stuff. Remove this once fhir path server is added
path = path.gsub(/.where\((.*)/, '')
as_type = path.scan(/.as\((.*?)\)/).flatten.first
path = path.gsub(/.as\((.*?)\)/, capitalize_first_letter(as_type)) if as_type.present?

"#{search_param_value_name(parameter)} = find_search_parameter_value_from_resource(@resource_found, '#{path}')"
end
search_test.code = %(
skip 'No resource found from Read test' unless @resource_found.present?
#{search_parameter_assignments.join("\n")}
search_parameters = {
#{search[:parameters].map { |parameter| "'#{parameter}': #{search_param_value_name(parameter)}" }.join(",\n")}
}

reply = get_resource_by_params(versioned_resource_class('#{metadata.resource_type}'), search_parameters)
validate_search_reply(versioned_resource_class('#{metadata.resource_type}'), reply, search_parameters)
)
metadata.add_test(search_test)
end
end

def search_param_value_name(parameter)
parameter.gsub!(/^[\W_]+|[\W_]+$"/, '') # remove non-character elements from beginning and end of name
"#{parameter.gsub('-', '_')}_val"
end

def capitalize_first_letter(str)
str.slice(0).capitalize + str.slice(1..-1)
end
end
end
end
3 changes: 3 additions & 0 deletions generator/generic/templates/module.yml.erb
Expand Up @@ -12,3 +12,6 @@ test_sets:
run_all: true
sequences:<% sequences.each do |sequence| %>
- <%= sequence.class_name %><% end %>
sequence_requirements:<% sequences.flat_map { |seq| seq.requirements }.uniq.map { |req| req.gsub(':', '') }.each do |requirement| %>
<%= requirement %>:
label: <%= requirement %><% end %>
4 changes: 4 additions & 0 deletions generator/generic/templates/sequence.rb.erb
Expand Up @@ -3,6 +3,8 @@
module Inferno
module Sequence
class <%= metadata.class_name %> < SequenceBase
include Inferno::SequenceUtilities

title '<%= metadata.title %> Tests'
description 'Verify support for the server capabilities required by the <%= metadata.title %> profile.'
details %(
Expand All @@ -12,6 +14,8 @@ module Inferno

@resource_found = nil

<%=metadata.create_search_validation(metadata)%>

<% metadata.tests.each_with_index do |test, idx|%>
<% if test.key.present? %>
test :<%= test.key %> do
Expand Down
9 changes: 9 additions & 0 deletions generator/generic/templates/sequence_definition.rb.erb
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module Inferno
module ProfileDefinitions
class <%= class_name %>
SEARCH_PARAMETERS = <%= search_parameters %>.freeze
end
end
end