shep 0.1.0.pre.alpha0

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 (67) hide show
  1. checksums.yaml +7 -0
  2. data/Copyright.txt +8 -0
  3. data/LICENSE.txt +697 -0
  4. data/README.md +101 -0
  5. data/Rakefile +52 -0
  6. data/doc/Shep/Entity/Account.html +193 -0
  7. data/doc/Shep/Entity/Context.html +165 -0
  8. data/doc/Shep/Entity/CustomEmoji.html +171 -0
  9. data/doc/Shep/Entity/MediaAttachment.html +175 -0
  10. data/doc/Shep/Entity/Notification.html +171 -0
  11. data/doc/Shep/Entity/Status.html +217 -0
  12. data/doc/Shep/Entity/StatusSource.html +167 -0
  13. data/doc/Shep/Entity/Status_Application.html +167 -0
  14. data/doc/Shep/Entity/Status_Mention.html +169 -0
  15. data/doc/Shep/Entity/Status_Tag.html +165 -0
  16. data/doc/Shep/Entity.html +1457 -0
  17. data/doc/Shep/Error/Caller.html +147 -0
  18. data/doc/Shep/Error/Http.html +329 -0
  19. data/doc/Shep/Error/Remote.html +143 -0
  20. data/doc/Shep/Error/Server.html +147 -0
  21. data/doc/Shep/Error/Type.html +233 -0
  22. data/doc/Shep/Error.html +149 -0
  23. data/doc/Shep/Session.html +4094 -0
  24. data/doc/Shep.html +128 -0
  25. data/doc/_index.html +300 -0
  26. data/doc/class_list.html +51 -0
  27. data/doc/css/common.css +1 -0
  28. data/doc/css/full_list.css +58 -0
  29. data/doc/css/style.css +497 -0
  30. data/doc/file.README.html +159 -0
  31. data/doc/file_list.html +56 -0
  32. data/doc/frames.html +17 -0
  33. data/doc/index.html +300 -0
  34. data/doc/js/app.js +314 -0
  35. data/doc/js/full_list.js +216 -0
  36. data/doc/js/jquery.js +4 -0
  37. data/doc/method_list.html +387 -0
  38. data/doc/top-level-namespace.html +110 -0
  39. data/lib/shep/entities.rb +164 -0
  40. data/lib/shep/entity_base.rb +378 -0
  41. data/lib/shep/exceptions.rb +78 -0
  42. data/lib/shep/session.rb +970 -0
  43. data/lib/shep/typeboxes.rb +180 -0
  44. data/lib/shep.rb +22 -0
  45. data/run_rake_test.example.sh +46 -0
  46. data/shep.gemspec +28 -0
  47. data/spec/data/smallimg.jpg +0 -0
  48. data/spec/data/smallish.jpg +0 -0
  49. data/spec/entity_common.rb +120 -0
  50. data/spec/entity_t1_spec.rb +168 -0
  51. data/spec/entity_t2_spec.rb +123 -0
  52. data/spec/entity_t3_spec.rb +30 -0
  53. data/spec/json_objects/account.1.json +25 -0
  54. data/spec/json_objects/account.2.json +36 -0
  55. data/spec/json_objects/status.1.json +85 -0
  56. data/spec/json_objects/status.2.json +59 -0
  57. data/spec/json_objects/status.3.json +95 -0
  58. data/spec/json_objects/status.4.json +95 -0
  59. data/spec/json_objects/status.5.json +74 -0
  60. data/spec/json_objects/status.6.json +140 -0
  61. data/spec/json_objects/status.7.json +84 -0
  62. data/spec/session_reader_1_unauth_spec.rb +366 -0
  63. data/spec/session_reader_2_auth_spec.rb +96 -0
  64. data/spec/session_writer_spec.rb +183 -0
  65. data/spec/spec_helper.rb +73 -0
  66. data/yard_helper.rb +30 -0
  67. metadata +154 -0
@@ -0,0 +1,180 @@
1
+
2
+
3
+ #
4
+ # This file is completely internal and so is not processed by yard.
5
+ #
6
+ # The "box" class heirarchy for Entity fields. These are used to
7
+ # specify the types of Entity fields. Specifically:
8
+ #
9
+ # 1. They enforce the expected types when assigning to the field.
10
+ #
11
+ # 2. They intelligently convert between Ruby types and Mastodon object
12
+ # JSON types. (e.g. Time <-> a string containing an ISO8601 date).
13
+ #
14
+ # 3. They are used to document the expected type.
15
+ #
16
+
17
+
18
+
19
+ module Shep
20
+ using Assert
21
+
22
+ private
23
+
24
+ # Abstract base classes for all type boxes. All scalar subclasses
25
+ # may also hold nil.
26
+ class TypeBox
27
+ def initialize(field_desc)
28
+ @desc = field_desc
29
+ @value = nil
30
+ end
31
+
32
+ def ==(other) = (other.class == self.class && other.get == @value)
33
+
34
+ def to_s = "#{self.class}(#{@value})"
35
+ def inspect = to_s
36
+
37
+ # Name for the Ruby type this holds as used by YARD. (This is
38
+ # part of making Entity self-documenting.)
39
+ def self.to_yard_s = self.to_s.split(/::/)[-1].gsub(/Box$/, '')
40
+
41
+ # Assignment from Ruby types with a type check.
42
+ def set(newval)
43
+ check(newval) unless newval == nil
44
+ @value = newval
45
+ end
46
+
47
+ def unset? = (@value == nil)
48
+ def set? = !unset?
49
+
50
+ def get = @value
51
+
52
+ # Set/Get in a format suitable for converting to JSON.
53
+ def get_for_json = @value
54
+ def set_from_json(newval) = set(newval)
55
+
56
+ protected
57
+
58
+ # Assert that 'new' is of one of the types in 'types'; raise an
59
+ # Error::Type if it is not.
60
+ def assert_type(new, *types)
61
+ raise Error::Type.new(@desc, new.class, *types) unless
62
+ types.include? new.class
63
+ end
64
+ end
65
+
66
+
67
+ class StringBox < TypeBox
68
+ def check(v) = assert_type(v, String)
69
+ end
70
+
71
+ class BoolBox < TypeBox
72
+ def self.to_yard_s = "Boolean"
73
+ def get_for_json = @value
74
+ def check(v) = assert_type(v, TrueClass, FalseClass)
75
+ end
76
+
77
+ class TimeBox < TypeBox
78
+ def check(v) = assert_type(v, Time)
79
+ def set_from_json(v)
80
+ set(Time.iso8601(v)) if v
81
+ end
82
+ def get_for_json = set? ? @value.utc.iso8601(6) : nil
83
+ end
84
+
85
+ class NumBox < TypeBox
86
+ def self.to_yard_s = "Integer" # a lie, but convenient
87
+ def get_for_json = @value
88
+ def check(v) = assert_type(v, Integer, Float)
89
+ end
90
+
91
+ class URIBox < TypeBox
92
+ def check(v)
93
+ raise Error::Type.new(@desc, v, URI::Generic) unless v.is_a?(URI::Generic)
94
+ end
95
+ def get_for_json = unset? ? @value : @value.to_s
96
+ def set_from_json(v) = set( (v == nil) ? v : URI(v) )
97
+ end
98
+
99
+ # Abstract base class for TypeBox subinstances that hold Entities
100
+ # (e.g. the Entity::Account field in a Status).
101
+ class OnDemandTypeBox < TypeBox
102
+ protected
103
+
104
+ def element_klass() raise Error.new("Used base class!") ; end
105
+
106
+ public
107
+
108
+ def self.wrapping(type)
109
+ @subklasses ||= {}
110
+
111
+ tid = type.__id__ # we use __id__ to ensure we're hashing by identity
112
+
113
+ unless @subklasses.has_key?(tid)
114
+ klassname = "#{self}[#{type}]"
115
+
116
+ sk = Class.new(self)
117
+ sk.define_singleton_method(:element_klass) { return type }
118
+ sk.define_singleton_method(:to_s) { return klassname }
119
+ sk.define_singleton_method(:inspect) { return klassname }
120
+ @subklasses[tid] = sk
121
+ end
122
+
123
+ return @subklasses[tid]
124
+ end
125
+
126
+ def element_klass = self.class.element_klass
127
+ end
128
+
129
+
130
+ class ArrayBox < OnDemandTypeBox
131
+ def initialize(field_desc)
132
+ super(field_desc)
133
+ element_klass # fails unless this is a subclass
134
+ end
135
+
136
+ def self.to_yard_s = "Array<#{self.element_klass.to_yard_s}>"
137
+
138
+ def get = basic_get(false)
139
+ def set(new_vals) = basic_set(new_vals, false)
140
+
141
+ def get_for_json = basic_get(true)
142
+ def set_from_json(new_vals) = basic_set(new_vals, true)
143
+
144
+ private
145
+
146
+ def basic_get(is_json)
147
+ return [].freeze unless @value
148
+ msg = is_json ? :get_for_json : :get
149
+ return @value.map{|valbox| valbox.send(msg) }.freeze
150
+ end
151
+
152
+ def basic_set(new_vals, is_json)
153
+ assert_type(new_vals, Array)
154
+ msg = is_json ? :set_from_json : :set
155
+ @value = new_vals.map{|item|
156
+ self.element_klass
157
+ .new("#{@field_desc}[#{@element_box_klass}]")
158
+ .then { |box|
159
+ box.send(msg, item)
160
+ next box
161
+ }
162
+ }
163
+ return new_vals
164
+ end
165
+ end
166
+
167
+ class EntityBox < OnDemandTypeBox
168
+ def check(v) = assert_type(v, self.element_klass)
169
+
170
+ def self.to_yard_s = self.element_klass.to_s.gsub(/^Shep::/,'')
171
+
172
+ def get_for_json = @value ? @value.to_h(true) : @value
173
+
174
+ def set_from_json(jhash)
175
+ return set(nil) if jhash == nil
176
+ set(self.element_klass.from(jhash))
177
+ end
178
+ end
179
+
180
+ end
data/lib/shep.rb ADDED
@@ -0,0 +1,22 @@
1
+
2
+ require 'net/https'
3
+
4
+ require 'uri'
5
+ require 'time'
6
+ require 'json'
7
+ require 'set'
8
+ require 'logger'
9
+ require 'securerandom'
10
+ require 'fileutils'
11
+
12
+ # SHEP: Simple Http intErface to Pmastodon
13
+ module Shep
14
+
15
+ end
16
+
17
+ require_relative 'shep/exceptions'
18
+ require_relative 'shep/typeboxes'
19
+ require_relative 'shep/entity_base'
20
+ require_relative 'shep/entities'
21
+ require_relative 'shep/session'
22
+
@@ -0,0 +1,46 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # This is an example script that sets up the remote server information
5
+ # needed to run the full suite of tests. Actual values have been
6
+ # replaced with nonsense.
7
+ #
8
+ # If none of these are set, only offline tests will be run
9
+
10
+
11
+ #
12
+ # Set these to enable basic read tests:
13
+ #
14
+
15
+ # The host
16
+ #export SHEP_TEST_HOST="mastodon.anti.social"
17
+
18
+ # The public account we read stuff from:
19
+ #export SHEP_READ_ACCOUNT_HANDLE="@somerandombot"
20
+ #export SHEP_READ_ACCOUNT=69420
21
+
22
+
23
+ #
24
+ # Set these if you also have an account that you wish to use for
25
+ # testing. This will only read from the account (we hope), but it's
26
+ # probably not a good idea to use an account you care about.
27
+ #
28
+
29
+ #export SHEP_ACCOUNT=54321
30
+ #export SHEP_TOKEN=JDJidvpsdvjapojSDFvfvafWER3qrwDVe4
31
+
32
+
33
+ #
34
+ # Set this if you want the tests to also write to the account. If
35
+ # they run successfully, they should have deleted every status they
36
+ # create and no others.
37
+ #
38
+ # Note that deletions and media uploads (both things these tests do)
39
+ # are heavily rate-limited so we turn this test off by default.
40
+ #
41
+
42
+ #export SHEP_TEST_ALL=1
43
+
44
+
45
+ cd $(dirname "$0")/shep
46
+ rake test
data/shep.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'shep'
3
+ s.version = '0.1.0-alpha0'
4
+ s.date = '2023-06-03'
5
+ s.summary = "An alternate interface to the Mastodon Web API"
6
+ s.description = <<-EOF
7
+ Shep is a library that provides access to the Mastodon Web API. It
8
+ has no gem dependencies beyond the base Ruby (3.0.x) installation and
9
+ can be tested without a dedicated Mastodon server. It is intended as
10
+ a slightly more feature-rich alternative to the mastodon-api gem.
11
+ EOF
12
+ s.authors = ["Chris Reuter"]
13
+ s.email = 'chris@isplitonyourgrave.com'
14
+
15
+ # I'm just going to add everything so that if you've got the gem,
16
+ # you've also got the source distribution. Yay! Open source!
17
+ s.files = Dir.glob('doc/**/*') + `git ls-files`.split(/\n/)
18
+
19
+ s.required_ruby_version = '>= 3.0.0'
20
+ s.requirements << "A Ruby implementing 3.0.x syntax and the standard libs."
21
+
22
+ s.add_development_dependency "rspec", '~> 3.12.0'
23
+ s.add_development_dependency "yard", '~> 0.9.34'
24
+ s.add_development_dependency 'rake', '~> 13.0.6'
25
+
26
+ s.homepage = 'https://codeberg.org/suetanvil/shep'
27
+ s.license = 'AGPL with linking exemption'
28
+ end
Binary file
Binary file
@@ -0,0 +1,120 @@
1
+
2
+ # An arbitrary time in the past
3
+ Then = Time.new(2021,02,06,22,11,52,'Z')
4
+
5
+
6
+ # Test entity One (basic Entity)
7
+
8
+ TE_KEYS = %i{id uri time num bool}
9
+
10
+ class TestEntity < Entity
11
+ fields(
12
+ :id, %i{show}, # visible string
13
+ :uri, %i{show}, URIBox, # uri field
14
+ :time, TimeBox, # time field
15
+ :num, NumBox, # numeric field
16
+ :bool, BoolBox, # bool field
17
+ )
18
+ end
19
+
20
+ def new_te
21
+ return TestEntity.with(
22
+ id: "123",
23
+ uri: URI("http://example.com"),
24
+ time: Then,
25
+ num: 42,
26
+ bool: false
27
+ )
28
+ end
29
+
30
+
31
+ #
32
+ # Test Entity Two (Entity with array fields)
33
+ #
34
+
35
+ class TestEntity2 < Entity
36
+ fields(
37
+ :id, %i{show}, NumBox,
38
+ :names, [StringBox],
39
+ :dates, [TimeBox],
40
+ )
41
+ end
42
+
43
+ def new_te2
44
+ return TestEntity2.with(
45
+ id: 42,
46
+ names: ["Alice", "Bob", "Christa", "Daniel"],
47
+ dates: [Then, Then+1, Then+2],
48
+ )
49
+ end
50
+
51
+
52
+ #
53
+ # Test Entity Three (Entity with another Entity as field type)
54
+ #
55
+
56
+ class TestEntity3 < Entity
57
+ fields(
58
+ :thing, %i{show},
59
+ :nested, TestEntity2,
60
+ )
61
+ end
62
+
63
+ def new_te3
64
+ return TestEntity3.with(
65
+ thing: "some text",
66
+ nested: new_te2,
67
+ )
68
+ end
69
+
70
+ TE3_JSON_TXT = <<EOF
71
+ {
72
+ "thing": "some text",
73
+ "nested": {
74
+ "id": 42,
75
+ "names": [
76
+ "Alice",
77
+ "Bob",
78
+ "Christa",
79
+ "Daniel"
80
+ ],
81
+ "dates": [
82
+ "2021-02-06T22:11:52Z",
83
+ "2021-02-06T22:11:53Z",
84
+ "2021-02-06T22:11:54Z"
85
+ ]
86
+ }
87
+ }
88
+ EOF
89
+
90
+
91
+ #
92
+ # Test Entity with Array of Entities
93
+ #
94
+
95
+ class TestEntity_ArrayOfNested < Entity
96
+ fields(
97
+ :boring, %i{show}, NumBox,
98
+ :na, [TestEntity3],
99
+ )
100
+ end
101
+
102
+ def new_te_aon
103
+ te3 = new_te3
104
+
105
+ te3a = new_te3
106
+
107
+ te2a = new_te2
108
+ te2a.names = ["Emma", "Frank", "Georgia"]
109
+
110
+ te3a.nested = te2a
111
+
112
+ return TestEntity_ArrayOfNested.with(
113
+ boring: 1,
114
+ na: [te3, te3a],
115
+ )
116
+ end
117
+
118
+
119
+
120
+
@@ -0,0 +1,168 @@
1
+
2
+ require_relative '../lib/shep.rb'
3
+ require_relative 'spec_helper.rb'
4
+
5
+ include Shep
6
+
7
+ require_relative "entity_common.rb"
8
+
9
+ describe Entity, :offline do
10
+ it "can be initialized via keyword args" do
11
+ te = new_te()
12
+
13
+ expect( te.id ) .to eq "123"
14
+ expect( te.uri ) .to eq URI("http://example.com")
15
+ expect( te.time ) .to eq Then
16
+ expect( te.num ) .to eq 42
17
+ expect( te.bool ) .to eq false
18
+
19
+ end
20
+
21
+ it "does basic hash-like stuff" do
22
+ te = new_te()
23
+ expect( te.keys.to_set ) .to eq TE_KEYS.to_set
24
+
25
+ newvals = ["456", URI("http://bogus.com"), Time.now, 999, true]
26
+ TE_KEYS.zip(newvals).each {|k, v|
27
+ expect( te[k] ) .to eq te.send(k)
28
+ expect( te[k] ) .to_not eq v
29
+
30
+ te[k] = v
31
+ expect( te[k] ) .to eq v
32
+ }
33
+ end
34
+
35
+ # it "tests for completeness" do
36
+ # te = new_te()
37
+ # expect( te.complete? ) .to be true
38
+
39
+ # te2 = TestEntity.with(id: '999')
40
+ # expect( te2.complete? ) .to be false
41
+ # end
42
+
43
+ it "tests for equality" do
44
+ te = new_te()
45
+ te2 = new_te()
46
+
47
+ expect( te == te2 ) .to be true
48
+ expect( te2 == te ) .to be true
49
+
50
+ te.id = "something else"
51
+ expect( te == te2 ) .to be false
52
+ expect( te2 == te ) .to be false
53
+
54
+ end
55
+
56
+ it "does struct-like access" do
57
+ te = new_te()
58
+ expect( te.id ) .to be te[:id]
59
+ expect( te.uri ) .to be te[:uri]
60
+ expect( te.time ) .to be te[:time]
61
+ expect( te.num ) .to be te[:num]
62
+ expect( te.bool ) .to be te[:bool]
63
+
64
+ tmp = "xxx"
65
+ te.id = tmp
66
+ expect( te.id ) .to eq tmp
67
+
68
+ tmp = URI("http://zombo.com")
69
+ te.uri = tmp
70
+ expect( te.uri ) .to eq tmp
71
+
72
+ tmp = Then - 100000
73
+ te.time = tmp
74
+ expect( te.time ) .to eq tmp
75
+
76
+ tmp = 999
77
+ te.num = tmp
78
+ expect( te.num ) .to eq 999
79
+
80
+ tmp = true
81
+ te.bool = true
82
+ expect( te.bool ) .to eq tmp
83
+ end
84
+
85
+ it "enforces field types" do
86
+ te = new_te()
87
+
88
+ types = {
89
+ id: "hello",
90
+ uri: URI("http://bogus.com"),
91
+ time: Then - 1000000,
92
+ num: 123,
93
+ bool: true
94
+ }
95
+
96
+ count = 0
97
+ for fld in types.keys
98
+ expect( te[fld] ) .to_not eq types[fld]
99
+ te[fld] = types[fld]
100
+ expect( te[fld] ) .to eq types[fld]
101
+
102
+ types.keys.each{|other_fld|
103
+ next if fld == other_fld
104
+
105
+ expect{ te[fld] = types[other_fld] }
106
+ .to raise_error(Shep::Error::Type)
107
+ expect{ te.send("#{fld}=".intern, types[other_fld]) }
108
+ .to raise_error(Shep::Error::Type)
109
+
110
+ count += 1
111
+ }
112
+ end
113
+
114
+ # Sanity check to ensure that the above loops got evaluated.
115
+ expect( count ) .to eq(types.size * (types.size - 1))
116
+ end
117
+
118
+ it "allows null values" do
119
+ te = new_te()
120
+
121
+ TE_KEYS.each{|key|
122
+ prev = te[key]
123
+ expect( prev ) .to_not eq nil
124
+ te[key] = nil
125
+ expect( te[key] ) .to eq nil
126
+
127
+ te[key] = prev
128
+ expect( te[key] ) .to eq prev
129
+ }
130
+ end
131
+
132
+ it "can be initialized from Mastodon JSON entities" do
133
+ jv = '{"id":"123","uri":"http://example.com",' +
134
+ '"time":"2021-02-06T22:11:52Z","num":42, "bool":false}'
135
+ te = TestEntity.from(JSON.parse(jv))
136
+
137
+ ref_te = new_te
138
+ ref_te.keys.each{|k|
139
+ expect( te[k] ) .to eq ref_te[k]
140
+ }
141
+ end
142
+
143
+ it "implements a to_h that provides JSON-able values" do
144
+ jv = '{"id":"123","uri":"http://example.com",' +
145
+ '"time":"2021-02-06T22:11:52.000000Z","num":42, "bool":false}'
146
+ jvh = JSON.parse(jv)
147
+
148
+ te = TestEntity.from(jvh)
149
+
150
+ jvh2 = te.to_h(true)
151
+ expect( jvh2 ) .to eq jvh
152
+ end
153
+
154
+ it "implements a to_h that also provides Ruby objects" do
155
+ jv = '{"id":"123","uri":"http://example.com",' +
156
+ '"time":"2021-02-06T22:11:52Z","num":42, "bool":false}'
157
+ jvh = JSON.parse(jv)
158
+ te = TestEntity.from(jvh)
159
+
160
+ te_h = te.to_h
161
+
162
+ expect( te_h[:id].class ) .to be String
163
+ expect( te_h[:uri].is_a? URI::Generic ) .to be true
164
+ expect( te_h[:time].is_a? Time ) .to be true
165
+ expect( te_h[:num] ) .to eq 42
166
+ expect( te_h[:bool] ) .to be false
167
+ end
168
+ end
@@ -0,0 +1,123 @@
1
+
2
+ require_relative '../lib/shep.rb'
3
+ require_relative 'spec_helper.rb'
4
+
5
+ include Shep
6
+
7
+ require_relative "entity_common.rb"
8
+
9
+
10
+ describe Entity, :offline do
11
+ it "provides array access" do
12
+ te = new_te2
13
+
14
+ expect( te.names ) .to eq ["Alice", "Bob", "Christa", "Daniel"]
15
+
16
+ te.names = ["Eve", "Frank", "Georgia"]
17
+ expect( te.names ) .to eq ["Eve", "Frank", "Georgia"]
18
+ end
19
+
20
+ it "enforces array element types" do
21
+ te = new_te2
22
+
23
+ nn = ["Alice", nil, "Christa"]
24
+ te.names = nn
25
+ expect( te.names ) .to eq nn
26
+
27
+ expect{ te.names = ["a", 1, "2"] } .to raise_error(Shep::Error::Type)
28
+ expect{ te.names = nil } .to raise_error(Shep::Error::Type)
29
+
30
+ te.names = []
31
+ expect( te.names ) .to eq []
32
+ end
33
+
34
+ it "produces json arrays from array items" do
35
+ te = new_te2
36
+ tej = te.to_h(true)
37
+ expect( tej["names"] ) .to eq ["Alice", "Bob", "Christa", "Daniel"]
38
+
39
+ dates = tej["dates"].map{|datestr|
40
+ expect(datestr.class) .to be String
41
+ Time.iso8601(datestr)
42
+ }
43
+
44
+ expect( dates ) .to eq te.dates
45
+ end
46
+
47
+ it "produces ruby hashes as usual" do
48
+ te = new_te2
49
+ tej = te.to_h(false)
50
+ expect( tej[:names] ) .to eq ["Alice", "Bob", "Christa", "Daniel"]
51
+ expect( tej[:dates] ) .to eq te.dates
52
+ end
53
+
54
+ it "supports nested entities" do
55
+ te2 = new_te2
56
+ te3 = new_te3
57
+ expect( te3.nested ) .to eq te2
58
+
59
+ te2a = new_te2
60
+ te2a.id = 12345;
61
+ te2a.names = ["Stan", "Ollie"]
62
+
63
+ te3.nested = te2a
64
+ expect( te3.nested ) .to eq te2a
65
+
66
+ expect{ te3.nested = new_te3 } .to raise_error(Shep::Error::Type)
67
+ expect{ te3.nested = "some string" } .to raise_error(Shep::Error::Type)
68
+ end
69
+
70
+ it "(with nested Entity) produces and can be created from Ruby hash" do
71
+ te3 = new_te3
72
+ h = te3.to_h
73
+
74
+ te3a = TestEntity3.new
75
+ te3a.set_from_hash!(h)
76
+
77
+ expect( te3a ) .to eq te3
78
+ end
79
+
80
+ it "(with nested entity) can be initialized from JSON" do
81
+ te3_json = JSON.parse(TE3_JSON_TXT)
82
+ te3 = TestEntity3.from(te3_json)
83
+
84
+ te3a = new_te3
85
+ expect( te3 ) .to eq te3a
86
+ end
87
+
88
+ it "produces JSON hashes" do
89
+ te3 = new_te3
90
+ te3_jsonable_hash = te3.to_h(true)
91
+ te3_json_str = JSON.pretty_generate(te3_jsonable_hash)
92
+
93
+ te3a_jsonable_hash = JSON.parse(te3_json_str)
94
+ te3a = TestEntity3.from(te3a_jsonable_hash)
95
+
96
+ expect( te3a ) .to eq te3
97
+
98
+ te3a_js = JSON.pretty_generate( te3a.to_h(true) )
99
+ expect( te3a_js ) .to eq te3_json_str
100
+ end
101
+
102
+ it "with arrays of nested entities" do
103
+ tea = new_te_aon
104
+
105
+ expect( tea.na[0] ) .to eq new_te3
106
+
107
+ tea_h = tea.to_h(true)
108
+ tea2 = TestEntity_ArrayOfNested.from(tea_h)
109
+ expect( tea2 ) .to eq tea
110
+ end
111
+
112
+ it "supports nil values for nested entities" do
113
+ te3 = new_te3
114
+ te3.nested = nil
115
+ expect( te3.nested ) .to be nil
116
+
117
+ te3a_json = JSON.parse( '{"thing": "some text"}' )
118
+ te3a = TestEntity3.from(te3a_json)
119
+ expect( te3a.nested ) .to be nil
120
+ end
121
+
122
+
123
+ end