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.
- checksums.yaml +7 -0
- data/Copyright.txt +8 -0
- data/LICENSE.txt +697 -0
- data/README.md +101 -0
- data/Rakefile +52 -0
- data/doc/Shep/Entity/Account.html +193 -0
- data/doc/Shep/Entity/Context.html +165 -0
- data/doc/Shep/Entity/CustomEmoji.html +171 -0
- data/doc/Shep/Entity/MediaAttachment.html +175 -0
- data/doc/Shep/Entity/Notification.html +171 -0
- data/doc/Shep/Entity/Status.html +217 -0
- data/doc/Shep/Entity/StatusSource.html +167 -0
- data/doc/Shep/Entity/Status_Application.html +167 -0
- data/doc/Shep/Entity/Status_Mention.html +169 -0
- data/doc/Shep/Entity/Status_Tag.html +165 -0
- data/doc/Shep/Entity.html +1457 -0
- data/doc/Shep/Error/Caller.html +147 -0
- data/doc/Shep/Error/Http.html +329 -0
- data/doc/Shep/Error/Remote.html +143 -0
- data/doc/Shep/Error/Server.html +147 -0
- data/doc/Shep/Error/Type.html +233 -0
- data/doc/Shep/Error.html +149 -0
- data/doc/Shep/Session.html +4094 -0
- data/doc/Shep.html +128 -0
- data/doc/_index.html +300 -0
- data/doc/class_list.html +51 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +58 -0
- data/doc/css/style.css +497 -0
- data/doc/file.README.html +159 -0
- data/doc/file_list.html +56 -0
- data/doc/frames.html +17 -0
- data/doc/index.html +300 -0
- data/doc/js/app.js +314 -0
- data/doc/js/full_list.js +216 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +387 -0
- data/doc/top-level-namespace.html +110 -0
- data/lib/shep/entities.rb +164 -0
- data/lib/shep/entity_base.rb +378 -0
- data/lib/shep/exceptions.rb +78 -0
- data/lib/shep/session.rb +970 -0
- data/lib/shep/typeboxes.rb +180 -0
- data/lib/shep.rb +22 -0
- data/run_rake_test.example.sh +46 -0
- data/shep.gemspec +28 -0
- data/spec/data/smallimg.jpg +0 -0
- data/spec/data/smallish.jpg +0 -0
- data/spec/entity_common.rb +120 -0
- data/spec/entity_t1_spec.rb +168 -0
- data/spec/entity_t2_spec.rb +123 -0
- data/spec/entity_t3_spec.rb +30 -0
- data/spec/json_objects/account.1.json +25 -0
- data/spec/json_objects/account.2.json +36 -0
- data/spec/json_objects/status.1.json +85 -0
- data/spec/json_objects/status.2.json +59 -0
- data/spec/json_objects/status.3.json +95 -0
- data/spec/json_objects/status.4.json +95 -0
- data/spec/json_objects/status.5.json +74 -0
- data/spec/json_objects/status.6.json +140 -0
- data/spec/json_objects/status.7.json +84 -0
- data/spec/session_reader_1_unauth_spec.rb +366 -0
- data/spec/session_reader_2_auth_spec.rb +96 -0
- data/spec/session_writer_spec.rb +183 -0
- data/spec/spec_helper.rb +73 -0
- data/yard_helper.rb +30 -0
- 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
|