case_model 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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