case_model 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +4 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.lock +61 -0
  5. data/README.md +27 -0
  6. data/Rakefile +9 -0
  7. data/case_model.gemspec +17 -0
  8. data/lib/case_model.rb +3 -0
  9. data/lib/descriptions/states.n3 +47 -0
  10. data/lib/models/action.rb +48 -0
  11. data/lib/models/answer.rb +25 -0
  12. data/lib/models/application.rb +57 -0
  13. data/lib/models/capability.rb +23 -0
  14. data/lib/models/deployment_plan.rb +44 -0
  15. data/lib/models/device.rb +26 -0
  16. data/lib/models/ecosystem_host.rb +5 -0
  17. data/lib/models/http_request.rb +27 -0
  18. data/lib/models/location.rb +17 -0
  19. data/lib/models/ontology.rb +52 -0
  20. data/lib/models/question.rb +51 -0
  21. data/lib/models/question_reply_type.rb +14 -0
  22. data/lib/models/requirement.rb +17 -0
  23. data/lib/models/selection.rb +32 -0
  24. data/lib/models/state_change.rb +19 -0
  25. data/lib/models/temperature.rb +17 -0
  26. data/lib/ontology/http_vocabulary.rb +17 -0
  27. data/lib/ontology/local_vocabulary.rb +26 -0
  28. data/lib/ontology/math_vocabulary.rb +15 -0
  29. data/lib/ontology/rdf_vocabulary.rb +11 -0
  30. data/lib/ontology/state_vocabulary.rb +9 -0
  31. data/lib/services/application_by_capability_finder.rb +37 -0
  32. data/lib/services/eye_serializer.rb +19 -0
  33. data/lib/services/node_builder.rb +34 -0
  34. data/lib/services/node_query.rb +55 -0
  35. data/lib/services/question_references.rb +19 -0
  36. data/lib/services/reasoner.rb +77 -0
  37. data/lib/services/repository_proxy.rb +26 -0
  38. data/spec/spec_helper.rb +8 -0
  39. data/spec/unit/models/answer_spec.rb +43 -0
  40. data/spec/unit/models/application_spec.rb +43 -0
  41. data/spec/unit/models/deployment_plan_spec.rb +28 -0
  42. data/spec/unit/models/http_request_spec.rb +42 -0
  43. data/spec/unit/models/location_spec.rb +36 -0
  44. data/spec/unit/models/question_spec.rb +89 -0
  45. data/spec/unit/models/requirement_spec.rb +29 -0
  46. data/spec/unit/models/selection_spec.rb +48 -0
  47. data/spec/unit/models/state_change_spec.rb +33 -0
  48. data/spec/unit/ontology/local_vocabulary_spec.rb +9 -0
  49. data/spec/unit/ontology/ontology_spec.rb +62 -0
  50. data/spec/unit/services/application_by_capability_finder_spec.rb +81 -0
  51. data/spec/unit/services/eye_serializer_spec.rb +34 -0
  52. data/spec/unit/services/node_builder_spec.rb +56 -0
  53. data/spec/unit/services/node_query_spec.rb +74 -0
  54. data/spec/unit/services/reasoner_spec.rb +76 -0
  55. data/spec/unit/spec_helper.rb +1 -0
  56. metadata +117 -0
@@ -0,0 +1,17 @@
1
+ class HttpVocabulary
2
+ def self.method_missing(name, *arguments, &block)
3
+ uri_for(name)
4
+ end
5
+
6
+ def self.method_name
7
+ uri_for('methodName')
8
+ end
9
+
10
+ def self.request_uri
11
+ uri_for('requestURI')
12
+ end
13
+
14
+ def self.uri_for(name)
15
+ RDF::URI("http://www.w3.org/2011/http##{name}")
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ require 'rdf'
2
+
3
+ class LocalVocabulary
4
+ def self.method_missing(name, *arguments, &block)
5
+ uri_for(name)
6
+ end
7
+
8
+ def self.name
9
+ uri_for('name')
10
+ end
11
+
12
+ def self.question_type
13
+ uri_for('question')
14
+ end
15
+
16
+ def self.answer_type
17
+ uri_for('answer')
18
+ end
19
+
20
+ def self.uri_for(name)
21
+ RDF::URI("http://matus.tomlein.org/case/#{name}")
22
+ end
23
+ end
24
+
25
+ LV = LocalVocabulary
26
+ QV = LocalVocabulary
@@ -0,0 +1,15 @@
1
+ class MathVocabulary
2
+ def self.method_missing(name, *arguments, &block)
3
+ uri_for(name)
4
+ end
5
+
6
+ def self.less_than
7
+ uri_for('lessThan')
8
+ end
9
+
10
+ def self.uri_for(name)
11
+ RDF::URI("http://www.w3.org/2000/10/swap/math##{name}")
12
+ end
13
+ end
14
+
15
+ MV = MathVocabulary
@@ -0,0 +1,11 @@
1
+ class RDFVocabulary
2
+ def self.method_missing(name, *arguments, &block)
3
+ uri_for(name)
4
+ end
5
+
6
+ def self.uri_for(name)
7
+ RDF::URI("http://www.w3.org/1999/02/22-rdf-syntax-ns##{name}")
8
+ end
9
+ end
10
+
11
+ RDFV = RDFVocabulary
@@ -0,0 +1,9 @@
1
+ class StateVocabulary
2
+ def self.method_missing(name, *arguments, &block)
3
+ uri_for(name)
4
+ end
5
+
6
+ def self.uri_for(name)
7
+ RDF::URI("http://matus.tomlein.org/case/#{name}")
8
+ end
9
+ end
@@ -0,0 +1,37 @@
1
+ class ApplicationByCapabilityFinder
2
+ def initialize(ontology)
3
+ @ontology = ontology
4
+ end
5
+
6
+ def find(options)
7
+ solutions = RDF::Query.execute(@ontology, {
8
+ application: {
9
+ LV.hasDeploymentPlan => {
10
+ LV.capability => options
11
+ }
12
+ }
13
+ })
14
+
15
+ solutions.map do |solution|
16
+ solution[:application]
17
+ end.uniq.map do |node|
18
+ Application.new(node, @ontology)
19
+ end
20
+ end
21
+
22
+ def find_by_type(type)
23
+ find({ RDF.type => type })
24
+ end
25
+
26
+ def find_by_description(description)
27
+ find({ LV.description => description })
28
+ end
29
+
30
+ def find_by_actuator(actuator)
31
+ find({ LV.actuator => actuator })
32
+ end
33
+
34
+ def find_by_sensor(sensor)
35
+ find({ LV.sensor => sensor })
36
+ end
37
+ end
@@ -0,0 +1,19 @@
1
+ class EyeSerializer
2
+ def self.serialize_implication(facts, precondition, postcondition)
3
+ pre = serialize_graph(precondition)
4
+ post = serialize_graph(postcondition)
5
+ "#{facts.dump(:ntriples)}{#{pre}} => {#{post}}.\n"
6
+ end
7
+
8
+ def self.serialize_graph(graph)
9
+ statements = []
10
+ graph.each_statement do |statement|
11
+ statements << RDF::Statement.new({
12
+ subject: statement.subject,
13
+ predicate: statement.predicate,
14
+ object: statement.object
15
+ }).to_s + "\n"
16
+ end
17
+ statements.join
18
+ end
19
+ end
@@ -0,0 +1,34 @@
1
+ require 'securerandom'
2
+
3
+ class NodeBuilder
4
+ def self.create(properties, repository)
5
+ node = generate_node
6
+ query = NodeQuery.new node, repository
7
+
8
+ properties.each do |key, v|
9
+ values = if v.is_a?(Array)
10
+ v
11
+ else
12
+ [ v ]
13
+ end
14
+
15
+ values.each do |value|
16
+ query.set_value(key,
17
+ if value.is_a?(Hash)
18
+ create(value, repository)
19
+ else
20
+ value
21
+ end, false)
22
+ end
23
+ end
24
+
25
+ node
26
+ end
27
+
28
+ private
29
+
30
+ def self.generate_node
31
+ id = SecureRandom.hex
32
+ RDF::URI("http://example.org/#{id}")
33
+ end
34
+ end
@@ -0,0 +1,55 @@
1
+ class NodeQuery
2
+ attr_reader :node, :repository
3
+
4
+ def initialize(node, repository)
5
+ @node = node
6
+ @repository = repository
7
+ end
8
+
9
+ def value(predicate)
10
+ values(predicate).first
11
+ end
12
+
13
+ def values(predicate)
14
+ results = repository.query([ node, predicate, nil ])
15
+ results.map {|s| s.object }
16
+ end
17
+
18
+ def value_exists?(predicate)
19
+ values(predicate).any?
20
+ end
21
+
22
+ def set_value(predicate, object, replace = true)
23
+ delete_value(predicate) if replace
24
+ @repository << [ node, predicate, object ]
25
+ end
26
+
27
+ def predicates_and_objects
28
+ values = {}
29
+ repository.query([ node, nil, nil ]).each do |s|
30
+ if values.has_key? s.predicate
31
+ if values[s.predicate].is_a? Array
32
+ values[s.predicate] << s.object
33
+ else
34
+ values[s.predicate] = [
35
+ values[s.predicate], s.object
36
+ ]
37
+ end
38
+ else
39
+ values[s.predicate] = s.object
40
+ end
41
+ end
42
+ values
43
+ end
44
+
45
+ private
46
+
47
+ def delete_value(predicate)
48
+ # makes sure not to delete any values in subgraphs
49
+ repository.query([ node, predicate, nil ]).select do |s|
50
+ s.graph_name.nil?
51
+ end.each do |s|
52
+ repository.delete(s)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ module QuestionReferences
2
+ def has_question?
3
+ query.value_exists?(LV.requires_answering)
4
+ end
5
+
6
+ def has_condition?
7
+ query.value_exists?(LV.condition)
8
+ end
9
+
10
+ def question
11
+ node = query.value(LV.requires_answering)
12
+ return nil if node.nil?
13
+ Question.new(node, query.repository)
14
+ end
15
+
16
+ def question_answered?
17
+ question.has_answer?
18
+ end
19
+ end
@@ -0,0 +1,77 @@
1
+ require 'rdf/n3'
2
+ require 'tempfile'
3
+
4
+ class Reasoner
5
+ attr_reader :repository
6
+
7
+ def initialize(repository)
8
+ @repository = repository
9
+ end
10
+
11
+ # implication doesn't work well when a blank node is used in the
12
+ # condition, e.g.:
13
+ # {?device1 <http://...#type> _:vehicle .} => {...}
14
+ # will always result in the postcondition, because of _:vehicle
15
+ def imply(precondition, postcondition)
16
+ eye_input = EyeSerializer.serialize_implication(
17
+ repository.facts_only,
18
+ repository.graph(precondition),
19
+ repository.graph(postcondition)
20
+ )
21
+
22
+ eye_output = run_eye eye_input
23
+ parse_eye_output eye_output
24
+ end
25
+
26
+ # makes sure that the graph with name condition is valid
27
+ # to do that, it creates a temporary graph (:helper_graph) and tries to imply
28
+ # condition => helper_graph
29
+ # it creates a new temp_reasoner object so as not to modify the current repository
30
+ # with the helper_graph
31
+ def check_condition(condition)
32
+ temp_repository = @repository.clone
33
+ temp_repository << [ LV.condition, LV.was, LV.true, :helper_graph ]
34
+
35
+ temp_reasoner = Reasoner.new temp_repository
36
+ result = temp_reasoner.imply(condition, :helper_graph)
37
+
38
+ result.query([ LV.condition, LV.was, LV.true ]).any?
39
+ end
40
+
41
+ def load_and_process_n3(n3_input)
42
+ input = [
43
+ n3_input,
44
+ EyeSerializer.serialize_graph(repository.facts_only)
45
+ ].join
46
+
47
+ eye_output = run_eye input
48
+ parse_eye_output eye_output, @repository
49
+ @repository
50
+ end
51
+
52
+ private
53
+
54
+ def run_eye(input)
55
+ data = Tempfile.new('data')
56
+ data.write(input)
57
+ data.close
58
+ output = `eye --nope --pass #{data.path} 2> /dev/null`
59
+ data.unlink
60
+
61
+ output
62
+ end
63
+
64
+ def parse_eye_output(output, repo = nil)
65
+ output = remove_incompatible_prefix_from_eye_output output
66
+ repo = RDF::Repository.new if repo.nil?
67
+ reader = RDF::Reader.for(:n3).new(output)
68
+ reader.each_statement { |statement| repo << statement }
69
+ repo
70
+ end
71
+
72
+ def remove_incompatible_prefix_from_eye_output(output)
73
+ output.gsub(/PREFIX .+\n/) do |match|
74
+ match.sub('PREFIX ', '@prefix ') + '.'
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,26 @@
1
+ module RepositoryProxy
2
+ def graph(name)
3
+ graph_name = if name.is_a? Symbol; "_:#{name.to_s}"; else; name.to_s; end
4
+ g = RDF::Graph.new
5
+ repository.statements.each do |statement|
6
+ next if statement.graph_name.nil?
7
+ next unless statement.graph_name.to_s.include? graph_name
8
+
9
+ g << [ statement.subject, statement.predicate, statement.object ]
10
+ end
11
+ g
12
+ end
13
+
14
+ def facts_only
15
+ RDF::Graph.new do |g|
16
+ repository.statements.each do |statement|
17
+ g << statement if statement.graph_name.nil?
18
+ end
19
+ end
20
+ end
21
+
22
+ # proxy everything to the repository
23
+ def method_missing(method, *args, &block)
24
+ repository.send(method, *args, &block)
25
+ end
26
+ end
@@ -0,0 +1,8 @@
1
+ require_relative '../lib/case_model'
2
+ require 'webmock/rspec'
3
+
4
+ RSpec.configure do |config|
5
+ config.expect_with :rspec do |c|
6
+ c.syntax = :expect
7
+ end
8
+ end
@@ -0,0 +1,43 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe Answer do
4
+ let(:repo) do
5
+ RDF::Repository.new do |r|
6
+ # DEVICE
7
+ r << [:dev, RDF.type, LV.Device]
8
+ r << [:dev, LV.manufacturerName, 'Grundfos']
9
+ r << [:dev, LV.locatedAt, :device_location]
10
+ # QUESTION
11
+ r << [:device_location, RDF.type, QV.question_type]
12
+ r << [:device_location, QV.replyType, QV.Location]
13
+ r << [:device_location, QV.location_of, :dev]
14
+ r << [:device_location, QV.text, 'Where is the thing?']
15
+ # ANSWER
16
+ r << [:device_location, QV.has_answer, :answer]
17
+ r << [:answer, RDF.type, QV.answer_type]
18
+ r << [:answer, QV.answers, :device_location]
19
+ r << [:answer, RDF.type, QV.Location]
20
+ r << [:answer, LV.locationName, 'Living Room']
21
+ end
22
+ end
23
+
24
+ let(:ontology) { Ontology.new(repo) }
25
+ let(:question) { Question.new(:device_location, ontology) }
26
+ let(:answer) { question.answer }
27
+
28
+ context '#question.text' do
29
+ subject { answer.question.text }
30
+ it { is_expected.not_to eq(nil) }
31
+ it { is_expected.to eq('Where is the thing?') }
32
+ end
33
+
34
+ context '#reply_type' do
35
+ subject { answer.reply_type }
36
+ it { is_expected.to eq(:location) }
37
+ end
38
+
39
+ describe 'should have type Location' do
40
+ subject { answer }
41
+ it { is_expected.to be_kind_of Location }
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ require_relative '../spec_helper'
2
+
3
+ def create_repo
4
+ RDF::Repository.new do |r|
5
+ r << [:a, RDF.type, LV.Application]
6
+ r << [:a, RDF::Vocab::FOAF.name, 'Some App']
7
+ r << [:a, LV.description, 'Description of the app.']
8
+
9
+ r << [:a, LV.hasDeploymentPlan, :deployment_plan1]
10
+ r << [:a, LV.hasDeploymentPlan, :deployment_plan2]
11
+ r << [:deployment_plan1, LV.description, 'This needs to be so and so.']
12
+ r << [:deployment_plan2, LV.description, 'This needs to be so and so.']
13
+ end
14
+ end
15
+
16
+ describe Application do
17
+ let(:repo) { create_repo }
18
+ let(:ontology) { Ontology.new(repo) }
19
+ let(:app) { Application.new(:a, repo) }
20
+
21
+ context '#name' do
22
+ subject { app.name }
23
+ it { is_expected.to eq('Some App') }
24
+ end
25
+
26
+ context '#description' do
27
+ subject { app.description }
28
+ it { is_expected.to eq('Description of the app.') }
29
+ end
30
+
31
+ context '#deployment_plans' do
32
+ subject { app.deployment_plans.size }
33
+ it { is_expected.to eq 2 }
34
+
35
+ describe 'should not have doubles' do
36
+ before do
37
+ repo << [ :a, LV.hasDeploymentPlan, :deployment_plan2 ]
38
+ end
39
+
40
+ it { is_expected.to eq 2 }
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,28 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe DeploymentPlan do
4
+ let(:repo) { RDF::Repository.new }
5
+ let(:ontology) { Ontology.new(repo) }
6
+ let(:deployment_plan) { DeploymentPlan.new(:a, repo) }
7
+
8
+ before do
9
+ repo << [:deployment_plan, LV.description, 'Install the app']
10
+ end
11
+
12
+ context '#description' do
13
+ subject { deployment_plan.description }
14
+ it { is_expected.to eq('Install the app') }
15
+ end
16
+
17
+ context '#arguments' do
18
+ before do
19
+ arguments = RDF::List[ 'arg1', 'arg2' ]
20
+ repo << arguments
21
+ repo << [ :deployment_plan, LV.arguments, arguments ]
22
+ end
23
+ let(:arguments) { deployment_plan.arguments }
24
+
25
+ subject { arguments.to_a }
26
+ it { is_expected.to eq [ 'arg1', 'arg2' ] }
27
+ end
28
+ end
@@ -0,0 +1,42 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe HttpRequest do
4
+ let(:repo) { RDF::Repository.new }
5
+ let(:ontology) { Ontology.new(repo) }
6
+ let(:request) { HttpRequest.new(:r, repo) }
7
+
8
+ before do
9
+ repo << [ :r, HttpVocabulary.method_name, 'POST' ]
10
+ repo << [ :r, HttpVocabulary.body, 'the body' ]
11
+ end
12
+
13
+ context '#uri' do
14
+ subject { request.uri }
15
+
16
+ describe 'as literal' do
17
+ before { repo << [ :r, HttpVocabulary.request_uri, 'http://example.com' ] }
18
+
19
+ it { is_expected.to eq 'http://example.com' }
20
+ end
21
+
22
+ describe 'as URI' do
23
+ before { repo << [ :r, HttpVocabulary.request_uri, RDF::URI('http://example.eu') ] }
24
+
25
+ it { is_expected.to eq 'http://example.eu' }
26
+ end
27
+ end
28
+
29
+ context '#execute' do
30
+ before do
31
+ repo << [ :r, HttpVocabulary.request_uri, 'http://example.com' ]
32
+ stub_request(:post, 'example.com')
33
+ end
34
+
35
+ it 'makes the request' do
36
+ expect(request.execute).to eq true
37
+
38
+ expect(WebMock).to have_requested(:post, 'example.com').
39
+ with(:body => 'the body')
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,36 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe Location do
4
+ let(:repo) do
5
+ RDF::Repository.new do |r|
6
+ # ANSWER
7
+ r << [:location, RDF.type, QV.answer_type]
8
+ r << [:location, RDF.type, QV.location]
9
+ r << [:location, LV.locationName, 'Living Room']
10
+ end
11
+ end
12
+
13
+ let(:ontology) { Ontology.new(repo) }
14
+ let(:location) { Location.new(:location, repo) }
15
+
16
+ context '#name' do
17
+ subject { location.name }
18
+ it { is_expected.to eq('Living Room') }
19
+ end
20
+
21
+ context '#name=' do
22
+ before :each do
23
+ location.name = 'Kitchen'
24
+ end
25
+
26
+ subject { location.name }
27
+ it { is_expected.to eq('Kitchen') }
28
+
29
+ describe 'new instances should have the changed value' do
30
+ let(:new_answer) { Location.new(:location, repo) }
31
+
32
+ subject { new_answer.name }
33
+ it { is_expected.to eq('Kitchen') }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,89 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe Question do
4
+ let(:repo) do
5
+ RDF::Repository.new do |r|
6
+ r << [:a, RDF.type, LV.Application]
7
+ r << [:device_location, RDF.type, QV.question_type]
8
+ r << [:device_location, QV.text, 'Where is the thing?']
9
+ r << [:device_location, QV.replyType, QV.Location]
10
+ r << [:device_location, QV.location_of, :device]
11
+ r << [:dev, RDF.type, LV.Device]
12
+ r << [:dev, LV.manufacturerName, 'Apple']
13
+ end
14
+ end
15
+
16
+ let(:ontology) { Ontology.new(repo) }
17
+ let(:question) { Question.new(:device_location, repo) }
18
+
19
+ context '#text' do
20
+ subject { question.text }
21
+ it { is_expected.to eq('Where is the thing?') }
22
+ end
23
+
24
+ context '#reply_type' do
25
+ subject { question.reply_type }
26
+ it { is_expected.to eq(:location) }
27
+ end
28
+
29
+ describe 'without answer' do
30
+ context '#has_answer?' do
31
+ subject { question.has_answer? }
32
+ it { is_expected.to eq(false) }
33
+ end
34
+
35
+ context '#answer' do
36
+ subject { question.answer }
37
+ it { is_expected.to eq(nil) }
38
+ end
39
+
40
+ context '#answer!' do
41
+ let(:answer) { question.answer! }
42
+
43
+ subject { answer }
44
+ it { is_expected.not_to eq(nil) }
45
+
46
+ describe 'location answer properties' do
47
+ before :each do
48
+ answer.name = 'Balcony'
49
+ end
50
+
51
+ subject { answer.name }
52
+ it { is_expected.to eq('Balcony') }
53
+ end
54
+ end
55
+ end
56
+
57
+ describe 'with answer' do
58
+ before :each do
59
+ repo << [:answer, RDF.type, QV.answer_type]
60
+ repo << [:answer, QV.answers, :device_location]
61
+ repo << [:device_location, QV.has_answer, :answer]
62
+ end
63
+
64
+ context '#has_answer?' do
65
+ subject { question.has_answer? }
66
+ it { is_expected.to eq(true) }
67
+ end
68
+
69
+ context '#answer' do
70
+ let(:answer) { question.answer }
71
+ subject { answer }
72
+
73
+ it { is_expected.not_to eq(nil) }
74
+
75
+ context '#question.text' do
76
+ subject { answer.question.text }
77
+ it { is_expected.to eq(question.text) }
78
+ end
79
+ end
80
+
81
+ context '#answer!' do
82
+ it 'should have the same value as calling answer' do
83
+ with_bang = question.answer!.name
84
+ without_bang = question.answer.name
85
+ expect(with_bang).to eq(without_bang)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,29 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe Ontology do
4
+ let(:repo) { RDF::Repository.new }
5
+ let(:ontology) { Ontology.new(repo) }
6
+ let(:requirement) { Requirement.new(:req, repo) }
7
+
8
+ before do
9
+ repo << [ :req, LV.description, 'This needs to be so and so.' ]
10
+ end
11
+
12
+ context '#description' do
13
+ subject { requirement.description }
14
+ it { is_expected.to eq('This needs to be so and so.') }
15
+ end
16
+
17
+ context '#satisfied?' do
18
+ describe 'satisfied requirement' do
19
+ before { repo << [ :req, LV.satisfied, true ] }
20
+ subject { requirement.satisfied? }
21
+ it { is_expected.to eq true }
22
+ end
23
+
24
+ describe 'not satisfied requirement' do
25
+ subject { requirement.satisfied? }
26
+ it { is_expected.to eq false }
27
+ end
28
+ end
29
+ end