factbase 0.0.25 → 0.0.27

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.
data/lib/factbase.rb CHANGED
@@ -21,7 +21,6 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  require 'json'
24
- require 'nokogiri'
25
24
  require 'yaml'
26
25
 
27
26
  # Factbase.
@@ -29,16 +28,19 @@ require 'yaml'
29
28
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
29
  # License:: MIT
31
30
  class Factbase
31
+ # Current version of the gem (changed by .rultor.yml on every release)
32
+ VERSION = '0.0.27'
33
+
32
34
  # Constructor.
33
- def initialize
34
- @maps = []
35
+ def initialize(facts = [])
36
+ @maps = facts
35
37
  @mutex = Mutex.new
36
38
  end
37
39
 
38
- # Is it empty?
39
- # @return [Boolean] TRUE if there are no facts inside
40
- def empty?
41
- @maps.empty?
40
+ # Make a duplicate of this factbase.
41
+ # @return [Factbase] A new factbase
42
+ def dup
43
+ Factbase.new(@maps.dup)
42
44
  end
43
45
 
44
46
  # Size.
@@ -63,11 +65,11 @@ class Factbase
63
65
  # There is a Lisp-like syntax, for example:
64
66
  #
65
67
  # (eq title 'Object Thinking')
66
- # (gt time '2024-03-23T03:21:43')
68
+ # (gt time 2024-03-23T03:21:43Z)
67
69
  # (gt cost 42)
68
70
  # (exists seenBy)
69
71
  # (and
70
- # (eq foo 42)
72
+ # (eq foo 42.998)
71
73
  # (or
72
74
  # (gt bar 200)
73
75
  # (absent zzz)))
@@ -78,6 +80,17 @@ class Factbase
78
80
  Factbase::Query.new(@maps, @mutex, query)
79
81
  end
80
82
 
83
+ # Run an ACID transaction, which will either modify the factbase
84
+ # or rollback in case of an error.
85
+ def txn(this = self)
86
+ copy = this.dup
87
+ yield copy
88
+ @mutex.synchronize do
89
+ @maps = []
90
+ import(copy.export)
91
+ end
92
+ end
93
+
81
94
  # Export it into a chain of bytes.
82
95
  def export
83
96
  Marshal.dump(@maps)
@@ -85,44 +98,6 @@ class Factbase
85
98
 
86
99
  # Import from a chain of bytes.
87
100
  def import(bytes)
88
- # rubocop:disable Security/MarshalLoad
89
101
  @maps += Marshal.load(bytes)
90
- # rubocop:enable Security/MarshalLoad
91
- end
92
-
93
- # Convert the entire factbase into JSON.
94
- # @return [String] The factbase in JSON format
95
- def to_json(_ = nil)
96
- @maps.to_json
97
- end
98
-
99
- # Convert the entire factbase into XML.
100
- # @return [String] The factbase in XML format
101
- def to_xml
102
- Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
103
- xml.fb do
104
- @maps.each do |m|
105
- xml.f_ do
106
- m.each do |k, vv|
107
- if vv.is_a?(Array)
108
- xml.send(:"#{k}_") do
109
- vv.each do |v|
110
- xml.send(:v, v)
111
- end
112
- end
113
- else
114
- xml.send(:"#{k}_", vv)
115
- end
116
- end
117
- end
118
- end
119
- end
120
- end.to_xml
121
- end
122
-
123
- # Convert the entire factbase into YAML.
124
- # @return [String] The factbase in YAML format
125
- def to_yaml
126
- YAML.dump({ 'facts' => @maps })
127
102
  end
128
103
  end
@@ -22,6 +22,7 @@
22
22
 
23
23
  require 'minitest/autorun'
24
24
  require 'loog'
25
+ require_relative '../../lib/factbase'
25
26
  require_relative '../../lib/factbase/inv'
26
27
  require_relative '../../lib/factbase/pre'
27
28
 
@@ -73,7 +73,7 @@ class TestLooged < Minitest::Test
73
73
  fb.query('(not (exists bar))').delete!
74
74
  [
75
75
  'Inserted new fact',
76
- 'Set \'bar\' to "3" (Integer)',
76
+ 'Set \'bar\' to 3 (Integer)',
77
77
  'Found 1 fact(s) by \'(exists bar)\'',
78
78
  'Deleted 2 fact(s) by \'(not (exists bar))\''
79
79
  ].each do |s|
@@ -54,6 +54,9 @@ class TestQuery < Minitest::Test
54
54
  "(and (lt pi 100) \n\n (gt num 1000))" => 0,
55
55
  '(exists pi)' => 1,
56
56
  '(not (exists hello))' => 3,
57
+ '(gt (size num) 2)' => 1,
58
+ '(lt (size num) 2)' => 2,
59
+ '(eq (size hello) 0)' => 3,
57
60
  '(absent time)' => 2,
58
61
  '(and (absent time) (exists pi))' => 1,
59
62
  "(and (exists time) (not (\t\texists pi)))" => 1,
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'minitest/autorun'
24
+ require_relative '../../lib/factbase'
25
+ require_relative '../../lib/factbase/rules'
26
+
27
+ # Test.
28
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
+ # License:: MIT
31
+ class TestRules < Minitest::Test
32
+ def test_simple_checking
33
+ fb = Factbase::Rules.new(
34
+ Factbase.new,
35
+ '(when (exists first) (exists second))'
36
+ )
37
+ f1 = fb.insert
38
+ f1.second = 2
39
+ f1.first = 1
40
+ assert_raises do
41
+ f2 = fb.insert
42
+ f2.first = 1
43
+ end
44
+ end
45
+ end
@@ -45,6 +45,7 @@ class TestSyntax < Minitest::Test
45
45
  '(foo)',
46
46
  '(foo (bar) (zz 77) )',
47
47
  "(eq foo \n\n 'Hello, world!'\n)\n",
48
+ "# this is a comment\n(eq foo # test\n 42)\n\n# another comment\n",
48
49
  "(or ( a 4) (b 5) () (and () (c 5) \t\t(r 7 8s 8is 'Foo')))"
49
50
  ].each do |q|
50
51
  Factbase::Syntax.new(q).to_term
@@ -62,6 +63,7 @@ class TestSyntax < Minitest::Test
62
63
  "(foo x y z t f 42 'Hi!' 33)",
63
64
  '(foo (x) y z)',
64
65
  '(eq t 2024-05-25T19:43:48Z)',
66
+ '(eq t 2024-05-25T19:43:48Z)',
65
67
  '(eq t 3.1415926)',
66
68
  '(eq t 3.0e+21)',
67
69
  "(foo (x (f (t (y 42 'Hey you'))) (f) (r 3)) y z)"
@@ -81,7 +83,7 @@ class TestSyntax < Minitest::Test
81
83
  '(or (eq bar 888) (eq z 1))' => true,
82
84
  "(or (gt bar 100) (eq foo 'Hello, world!'))" => true
83
85
  }.each do |k, v|
84
- assert_equal(v, Factbase::Syntax.new(k).to_term.matches?(m), k)
86
+ assert_equal(v, Factbase::Syntax.new(k).to_term.eval(m), k)
85
87
  end
86
88
  end
87
89
 
@@ -89,6 +91,7 @@ class TestSyntax < Minitest::Test
89
91
  [
90
92
  '',
91
93
  '(foo',
94
+ 'some text',
92
95
  '"hello, world!',
93
96
  '(foo 7',
94
97
  "(foo 7 'Dude'",
@@ -30,79 +30,95 @@ require_relative '../../lib/factbase/term'
30
30
  class TestTerm < Minitest::Test
31
31
  def test_simple_matching
32
32
  t = Factbase::Term.new(:eq, [:foo, 42])
33
- assert(t.matches?(fact('foo' => [42])))
34
- assert(!t.matches?(fact('foo' => 'Hello!')))
35
- assert(!t.matches?(fact('bar' => ['Hello!'])))
33
+ assert(t.eval(fact('foo' => [42])))
34
+ assert(!t.eval(fact('foo' => 'Hello!')))
35
+ assert(!t.eval(fact('bar' => ['Hello!'])))
36
36
  end
37
37
 
38
38
  def test_eq_matching
39
39
  t = Factbase::Term.new(:eq, [:foo, 42])
40
- assert(t.matches?(fact('foo' => 42)))
41
- assert(t.matches?(fact('foo' => [10, 5, 6, -8, 'hey', 42, 9, 'fdsf'])))
42
- assert(!t.matches?(fact('foo' => [100])))
43
- assert(!t.matches?(fact('foo' => [])))
44
- assert(!t.matches?(fact('bar' => [])))
40
+ assert(t.eval(fact('foo' => 42)))
41
+ assert(t.eval(fact('foo' => [10, 5, 6, -8, 'hey', 42, 9, 'fdsf'])))
42
+ assert(!t.eval(fact('foo' => [100])))
43
+ assert(!t.eval(fact('foo' => [])))
44
+ assert(!t.eval(fact('bar' => [])))
45
45
  end
46
46
 
47
47
  def test_eq_matching_time
48
48
  now = Time.now
49
49
  t = Factbase::Term.new(:eq, [:foo, Time.parse(now.iso8601)])
50
- assert(t.matches?(fact('foo' => now)))
51
- assert(t.matches?(fact('foo' => [now, Time.now])))
50
+ assert(t.eval(fact('foo' => now)))
51
+ assert(t.eval(fact('foo' => [now, Time.now])))
52
52
  end
53
53
 
54
54
  def test_lt_matching
55
55
  t = Factbase::Term.new(:lt, [:foo, 42])
56
- assert(t.matches?(fact('foo' => [10])))
57
- assert(!t.matches?(fact('foo' => [100])))
58
- assert(!t.matches?(fact('foo' => 100)))
59
- assert(!t.matches?(fact('bar' => 100)))
56
+ assert(t.eval(fact('foo' => [10])))
57
+ assert(!t.eval(fact('foo' => [100])))
58
+ assert(!t.eval(fact('foo' => 100)))
59
+ assert(!t.eval(fact('bar' => 100)))
60
60
  end
61
61
 
62
62
  def test_gt_matching
63
63
  t = Factbase::Term.new(:gt, [:foo, 42])
64
- assert(t.matches?(fact('foo' => [100])))
65
- assert(t.matches?(fact('foo' => 100)))
66
- assert(!t.matches?(fact('foo' => [10])))
67
- assert(!t.matches?(fact('foo' => 10)))
68
- assert(!t.matches?(fact('bar' => 10)))
64
+ assert(t.eval(fact('foo' => [100])))
65
+ assert(t.eval(fact('foo' => 100)))
66
+ assert(!t.eval(fact('foo' => [10])))
67
+ assert(!t.eval(fact('foo' => 10)))
68
+ assert(!t.eval(fact('bar' => 10)))
69
69
  end
70
70
 
71
71
  def test_lt_matching_time
72
72
  t = Factbase::Term.new(:lt, [:foo, Time.now])
73
- assert(t.matches?(fact('foo' => [Time.now - 100])))
74
- assert(!t.matches?(fact('foo' => [Time.now + 100])))
75
- assert(!t.matches?(fact('bar' => [100])))
73
+ assert(t.eval(fact('foo' => [Time.now - 100])))
74
+ assert(!t.eval(fact('foo' => [Time.now + 100])))
75
+ assert(!t.eval(fact('bar' => [100])))
76
76
  end
77
77
 
78
78
  def test_gt_matching_time
79
79
  t = Factbase::Term.new(:gt, [:foo, Time.now])
80
- assert(t.matches?(fact('foo' => [Time.now + 100])))
81
- assert(!t.matches?(fact('foo' => [Time.now - 100])))
82
- assert(!t.matches?(fact('bar' => [100])))
80
+ assert(t.eval(fact('foo' => [Time.now + 100])))
81
+ assert(!t.eval(fact('foo' => [Time.now - 100])))
82
+ assert(!t.eval(fact('bar' => [100])))
83
83
  end
84
84
 
85
85
  def test_not_matching
86
86
  t = Factbase::Term.new(:not, [Factbase::Term.new(:nil, [])])
87
- assert(!t.matches?(fact('foo' => [100])))
87
+ assert(!t.eval(fact('foo' => [100])))
88
88
  end
89
89
 
90
90
  def test_not_eq_matching
91
91
  t = Factbase::Term.new(:not, [Factbase::Term.new(:eq, [:foo, 100])])
92
- assert(t.matches?(fact('foo' => [42, 12, -90])))
93
- assert(!t.matches?(fact('foo' => 100)))
92
+ assert(t.eval(fact('foo' => [42, 12, -90])))
93
+ assert(!t.eval(fact('foo' => 100)))
94
+ end
95
+
96
+ def test_size_matching
97
+ t = Factbase::Term.new(:size, [:foo])
98
+ assert_equal(3, t.eval(fact('foo' => [42, 12, -90])))
99
+ assert_equal(0, t.eval(fact('bar' => 100)))
94
100
  end
95
101
 
96
102
  def test_exists_matching
97
103
  t = Factbase::Term.new(:exists, [:foo])
98
- assert(t.matches?(fact('foo' => [42, 12, -90])))
99
- assert(!t.matches?(fact('bar' => 100)))
104
+ assert(t.eval(fact('foo' => [42, 12, -90])))
105
+ assert(!t.eval(fact('bar' => 100)))
100
106
  end
101
107
 
102
108
  def test_absent_matching
103
109
  t = Factbase::Term.new(:absent, [:foo])
104
- assert(t.matches?(fact('z' => [42, 12, -90])))
105
- assert(!t.matches?(fact('foo' => 100)))
110
+ assert(t.eval(fact('z' => [42, 12, -90])))
111
+ assert(!t.eval(fact('foo' => 100)))
112
+ end
113
+
114
+ def test_type_matching
115
+ t = Factbase::Term.new(:type, [:foo])
116
+ assert_equal('Integer', t.eval(fact('foo' => 42)))
117
+ assert_equal('Array', t.eval(fact('foo' => [1, 2, 3])))
118
+ assert_equal('String', t.eval(fact('foo' => 'Hello, world!')))
119
+ assert_equal('Float', t.eval(fact('foo' => 3.14)))
120
+ assert_equal('Time', t.eval(fact('foo' => Time.now)))
121
+ assert_equal('nil', t.eval(fact))
106
122
  end
107
123
 
108
124
  def test_or_matching
@@ -113,14 +129,27 @@ class TestTerm < Minitest::Test
113
129
  Factbase::Term.new(:eq, [:bar, 5])
114
130
  ]
115
131
  )
116
- assert(t.matches?(fact('foo' => [4])))
117
- assert(t.matches?(fact('bar' => [5])))
118
- assert(!t.matches?(fact('bar' => [42])))
132
+ assert(t.eval(fact('foo' => [4])))
133
+ assert(t.eval(fact('bar' => [5])))
134
+ assert(!t.eval(fact('bar' => [42])))
135
+ end
136
+
137
+ def test_when_matching
138
+ t = Factbase::Term.new(
139
+ :when,
140
+ [
141
+ Factbase::Term.new(:eq, [:foo, 4]),
142
+ Factbase::Term.new(:eq, [:bar, 5])
143
+ ]
144
+ )
145
+ assert(t.eval(fact('foo' => 4, 'bar' => 5)))
146
+ assert(!t.eval(fact('foo' => 4)))
147
+ assert(t.eval(fact('foo' => 5, 'bar' => 5)))
119
148
  end
120
149
 
121
150
  private
122
151
 
123
- def fact(map)
152
+ def fact(map = {})
124
153
  Factbase::Fact.new(Mutex.new, map)
125
154
  end
126
155
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'minitest/autorun'
24
+ require 'loog'
25
+ require_relative '../../lib/factbase'
26
+ require_relative '../../lib/factbase/to_json'
27
+
28
+ # Test.
29
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
30
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
31
+ # License:: MIT
32
+ class TestToJSON < Minitest::Test
33
+ def test_simple_rendering
34
+ fb = Factbase.new
35
+ f = fb.insert
36
+ f.foo = 42
37
+ f.foo = 256
38
+ to = Factbase::ToJSON.new(fb)
39
+ json = JSON.parse(to.json)
40
+ assert(42, json[0]['foo'][1])
41
+ end
42
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'minitest/autorun'
24
+ require 'loog'
25
+ require_relative '../../lib/factbase'
26
+ require_relative '../../lib/factbase/to_xml'
27
+
28
+ # Test.
29
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
30
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
31
+ # License:: MIT
32
+ class TestToXML < Minitest::Test
33
+ def test_simple_rendering
34
+ fb = Factbase.new
35
+ fb.insert.t = Time.now
36
+ to = Factbase::ToXML.new(fb)
37
+ xml = Nokogiri::XML.parse(to.xml)
38
+ assert(!xml.xpath('/fb/f[t]').empty?)
39
+ assert(
40
+ xml.xpath('/fb/f/t/text()').text.match?(
41
+ /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/
42
+ )
43
+ )
44
+ end
45
+
46
+ def test_complex_rendering
47
+ fb = Factbase.new
48
+ fb.insert.t = "\uffff < > & ' \""
49
+ to = Factbase::ToXML.new(fb)
50
+ xml = Nokogiri::XML.parse(to.xml)
51
+ assert(!xml.xpath('/fb/f[t]').empty?)
52
+ end
53
+
54
+ def test_meta_data_presence
55
+ fb = Factbase.new
56
+ fb.insert.x = 42
57
+ to = Factbase::ToXML.new(fb)
58
+ xml = Nokogiri::XML.parse(to.xml)
59
+ assert(!xml.xpath('/fb[@dob]').empty?)
60
+ assert(!xml.xpath('/fb[@factbase_version]').empty?)
61
+ end
62
+
63
+ def test_to_xml_with_short_names
64
+ fb = Factbase.new
65
+ f = fb.insert
66
+ f.type = 1
67
+ f.f = 2
68
+ f.class = 3
69
+ to = Factbase::ToXML.new(fb)
70
+ xml = Nokogiri::XML.parse(to.xml)
71
+ assert(!xml.xpath('/fb/f/type').empty?)
72
+ assert(!xml.xpath('/fb/f/f').empty?)
73
+ assert(!xml.xpath('/fb/f/class').empty?)
74
+ end
75
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'minitest/autorun'
24
+ require 'loog'
25
+ require_relative '../../lib/factbase'
26
+ require_relative '../../lib/factbase/to_yaml'
27
+
28
+ # Test.
29
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
30
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
31
+ # License:: MIT
32
+ class TestToYAML < Minitest::Test
33
+ def test_simple_rendering
34
+ fb = Factbase.new
35
+ f = fb.insert
36
+ f.foo = 42
37
+ f.foo = 256
38
+ fb.insert
39
+ to = Factbase::ToYAML.new(fb)
40
+ yaml = YAML.load(to.yaml)
41
+ assert_equal(2, yaml['facts'].size)
42
+ assert_equal(42, yaml['facts'][0]['foo'][0])
43
+ assert_equal(256, yaml['facts'][0]['foo'][1])
44
+ end
45
+ end
@@ -21,9 +21,7 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  require 'minitest/autorun'
24
- require 'json'
25
- require 'nokogiri'
26
- require 'yaml'
24
+ require 'loog'
27
25
  require_relative '../lib/factbase'
28
26
 
29
27
  # Factbase main module test.
@@ -59,52 +57,76 @@ class TestFactbase < Minitest::Test
59
57
 
60
58
  def test_empty_or_not
61
59
  fb = Factbase.new
62
- assert(fb.empty?)
60
+ assert_equal(0, fb.size)
63
61
  fb.insert
64
- assert(!fb.empty?)
62
+ assert_equal(1, fb.size)
65
63
  end
66
64
 
67
- def test_to_json
68
- fb = Factbase.new
69
- f = fb.insert
70
- f.foo = 42
71
- f.foo = 256
72
- json = JSON.parse(fb.to_json)
73
- assert(42, json[0]['foo'][1])
65
+ def test_makes_duplicate
66
+ fb1 = Factbase.new
67
+ fb1.insert
68
+ assert_equal(1, fb1.size)
69
+ fb2 = fb1.dup
70
+ fb2.insert
71
+ assert_equal(1, fb1.size)
72
+ assert_equal(2, fb2.size)
74
73
  end
75
74
 
76
- def test_to_xml
75
+ def test_run_txn
77
76
  fb = Factbase.new
78
- f = fb.insert
79
- f.foo = 42
80
- f.foo = 256
81
- fb.insert.bar = 5
82
- xml = Nokogiri::XML.parse(fb.to_xml)
83
- assert(!xml.xpath('/fb[count(f) = 2]').empty?)
84
- assert(!xml.xpath('/fb/f/foo[v="42"]').empty?)
77
+ fb.txn do |fbt|
78
+ fbt.insert.bar = 42
79
+ fbt.insert.z = 42
80
+ end
81
+ assert_equal(2, fb.size)
82
+ assert(
83
+ assert_raises do
84
+ fb.txn do |fbt|
85
+ fbt.insert.foo = 42
86
+ throw 'intentionally'
87
+ end
88
+ end.message.include?('intentionally')
89
+ )
90
+ assert_equal(2, fb.size)
85
91
  end
86
92
 
87
- def test_to_xml_with_short_names
88
- fb = Factbase.new
89
- f = fb.insert
90
- f.type = 1
91
- f.f = 2
92
- f.class = 3
93
- xml = Nokogiri::XML.parse(fb.to_xml)
94
- assert(!xml.xpath('/fb/f/type').empty?)
95
- assert(!xml.xpath('/fb/f/f').empty?)
96
- assert(!xml.xpath('/fb/f/class').empty?)
93
+ def test_run_txn_with_inv
94
+ fb = Factbase::Inv.new(Factbase.new) { |_p, v| throw 'oops' if v == 42 }
95
+ fb.insert.bar = 3
96
+ fb.insert.foo = 5
97
+ assert_equal(2, fb.size)
98
+ assert(
99
+ assert_raises do
100
+ fb.txn do |fbt|
101
+ fbt.insert.foo = 42
102
+ end
103
+ end.message.include?('oops')
104
+ )
105
+ assert_equal(2, fb.size)
97
106
  end
98
107
 
99
- def test_to_yaml
100
- fb = Factbase.new
101
- f = fb.insert
102
- f.foo = 42
103
- f.foo = 256
104
- fb.insert
105
- yaml = YAML.load(fb.to_yaml)
106
- assert_equal(2, yaml['facts'].size)
107
- assert_equal(42, yaml['facts'][0]['foo'][0])
108
- assert_equal(256, yaml['facts'][0]['foo'][1])
108
+ def test_all_decorators
109
+ [
110
+ Factbase::Rules.new(Factbase.new, '()'),
111
+ Factbase::Inv.new(Factbase.new) { |_, _| true },
112
+ Factbase::Pre.new(Factbase.new) { |_| true },
113
+ Factbase::Looged.new(Factbase.new, Loog::NULL),
114
+ Factbase::Spy.new(Factbase.new, 'ff')
115
+ ].each do |d|
116
+ f = d.insert
117
+ f.foo = 42
118
+ d.txn do |fbt|
119
+ fbt.insert.bar = 455
120
+ end
121
+ assert_raises do
122
+ d.txn do |fbt|
123
+ fbt.insert
124
+ throw 'oops'
125
+ end
126
+ end
127
+ d.import(d.export)
128
+ assert_equal(4, d.size)
129
+ assert_equal(4, d.query('()').each.to_a.size)
130
+ end
109
131
  end
110
132
  end