timberline 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.travis.yml +10 -0
- data/Gemfile +1 -1
- data/README.markdown +11 -6
- data/Rakefile +4 -5
- data/lib/timberline/config.rb +1 -0
- data/lib/timberline/queue.rb +4 -1
- data/lib/timberline/version.rb +1 -1
- data/lib/timberline.rb +108 -114
- data/spec/fake_rails/config/timberline.yaml +7 -0
- data/spec/lib/timberline/config_spec.rb +170 -0
- data/spec/lib/timberline/envelope_spec.rb +91 -0
- data/spec/lib/timberline/queue_spec.rb +100 -0
- data/spec/lib/timberline_spec.rb +375 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/support/fake_rails.rb +17 -0
- data/spec/support/timberline_reset.rb +20 -0
- data/spec/support/timberline_yaml.rb +17 -0
- data/timberline.gemspec +4 -5
- metadata +63 -53
- data/test/fake_rails/config/timberline.yaml +0 -14
- data/test/partial_minispec.rb +0 -229
- data/test/test_helper.rb +0 -25
- data/test/unit/config_test.rb +0 -122
- data/test/unit/envelope_test.rb +0 -69
- data/test/unit/queue_test.rb +0 -114
- data/test/unit/timberline_test.rb +0 -143
- /data/{test → spec/config}/test_config.yaml +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 01da0a8b72d0153c044386ba5d2a2947ceba809a
|
4
|
+
data.tar.gz: b56730906aa005b6107cea4f1c693362e8a1ee99
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e997f4fc10c86d1c64f2f4a18982e1fa9a2a30042d078988c3db13b1f675f7ca5260bad1b45a9b75b055292ff5e0a8d712e0fc7fe5e9ab0804d417510d0b71a2
|
7
|
+
data.tar.gz: a4c464628b537811773da27a6f95828bba46566b23bd53bfec452d17497dd99a464ef8c7adb3373df1f0ee4fd1d5534e42cbc9de8fb6a3ac18b860c4be747e7e
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
timberline
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.markdown
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Timberline
|
2
2
|
|
3
|
+
![](https://travis-ci.org/treehouse/timberline.svg)
|
4
|
+
|
3
5
|
## Purpose
|
4
6
|
|
5
7
|
Timberline is a dead-simple queuing service written in Ruby and backed by Redis.
|
@@ -64,7 +66,7 @@ queue (more on that later). There are 3 ways to configure Timberline:
|
|
64
66
|
|
65
67
|
1. The most direct way is to configure Timberline via ruby code as follows:
|
66
68
|
|
67
|
-
Timberline.
|
69
|
+
Timberline.configure do |c|
|
68
70
|
c.database = 1
|
69
71
|
c.host = "192.168.1.105"
|
70
72
|
c.port = 12345
|
@@ -81,10 +83,13 @@ queue (more on that later). There are 3 ways to configure Timberline:
|
|
81
83
|
automatically detect it and load it up. The syntax for this file is
|
82
84
|
shockingly boring:
|
83
85
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
86
|
+
development:
|
87
|
+
database: 1
|
88
|
+
host: 192.168.1.105
|
89
|
+
port: 12345
|
90
|
+
password: foobar
|
91
|
+
production:
|
92
|
+
...etc.
|
88
93
|
|
89
94
|
3. Like the yaml format but you're not using Rails? Don't worry, just write your
|
90
95
|
yaml file and set the TIMBERLINE\_YAML constant inside your app like so:
|
@@ -205,7 +210,7 @@ should probably change, but it probably won't until someone needs it to.
|
|
205
210
|
Gem requirements/etc. should be handled by Bundler.
|
206
211
|
|
207
212
|
## License
|
208
|
-
Copyright (C)
|
213
|
+
Copyright (C) 2014 by Tommy Morgan
|
209
214
|
|
210
215
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
211
216
|
|
data/Rakefile
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rake/testtask"
|
3
|
+
require 'rspec/core/rake_task'
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
t.verbose = true
|
8
|
-
end
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
task :default => :spec
|
data/lib/timberline/config.rb
CHANGED
data/lib/timberline/queue.rb
CHANGED
@@ -3,13 +3,16 @@ class Timberline
|
|
3
3
|
attr_reader :queue_name, :read_timeout
|
4
4
|
|
5
5
|
def initialize(queue_name, read_timeout= 0)
|
6
|
+
if queue_name.nil?
|
7
|
+
raise ArgumentError.new("Queue name must be provided.")
|
8
|
+
end
|
6
9
|
@queue_name = queue_name
|
7
10
|
@read_timeout = read_timeout
|
8
11
|
@redis = Timberline.redis
|
9
12
|
@redis.sadd "timberline_queue_names", queue_name
|
10
13
|
end
|
11
14
|
|
12
|
-
def delete
|
15
|
+
def delete
|
13
16
|
@redis.del @queue_name
|
14
17
|
@redis.keys("#{@queue_name}:*").each do |key|
|
15
18
|
@redis.del key
|
data/lib/timberline/version.rb
CHANGED
data/lib/timberline.rb
CHANGED
@@ -13,141 +13,137 @@ require_relative "timberline/envelope"
|
|
13
13
|
|
14
14
|
class Timberline
|
15
15
|
class << self
|
16
|
-
|
17
16
|
attr_reader :config
|
17
|
+
attr_accessor :watch_proc
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
20
|
+
def self.redis=(server)
|
21
|
+
initialize_if_necessary
|
22
|
+
if server.is_a? Redis
|
23
|
+
@redis = Redis::Namespace.new(@config.namespace, :redis => server)
|
24
|
+
elsif server.is_a? Redis::Namespace
|
25
|
+
@redis = server
|
26
|
+
elsif server.nil?
|
27
|
+
@redis = nil
|
28
|
+
else
|
29
|
+
raise "Not a valid Redis connection."
|
30
30
|
end
|
31
|
+
end
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
37
|
-
|
38
|
-
@redis
|
33
|
+
def self.redis
|
34
|
+
initialize_if_necessary
|
35
|
+
if @redis.nil?
|
36
|
+
self.redis = Redis.new(@config.redis_config)
|
39
37
|
end
|
40
38
|
|
41
|
-
|
42
|
-
|
43
|
-
end
|
39
|
+
@redis
|
40
|
+
end
|
44
41
|
|
45
|
-
|
46
|
-
|
47
|
-
|
42
|
+
def self.all_queues
|
43
|
+
Timberline.redis.smembers("timberline_queue_names").map { |name| queue(name) }
|
44
|
+
end
|
48
45
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
53
|
-
@queue_list[queue_name]
|
54
|
-
end
|
46
|
+
def self.error_queue
|
47
|
+
@error_queue ||= Queue.new("timberline_errors")
|
48
|
+
end
|
55
49
|
|
56
|
-
|
57
|
-
|
58
|
-
|
50
|
+
def self.queue(queue_name)
|
51
|
+
Queue.new(queue_name)
|
52
|
+
end
|
59
53
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
item.last_tried_at = Time.now.to_f
|
64
|
-
queue(item.origin_queue).push(item)
|
65
|
-
raise ItemRetried
|
66
|
-
else
|
67
|
-
error_item(item)
|
68
|
-
end
|
69
|
-
end
|
54
|
+
def self.push(queue_name, data, metadata={})
|
55
|
+
queue(queue_name).push(data, metadata)
|
56
|
+
end
|
70
57
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
58
|
+
def self.retry_item(item)
|
59
|
+
if (item.retries < max_retries)
|
60
|
+
item.retries += 1
|
61
|
+
item.last_tried_at = Time.now.to_f
|
62
|
+
queue(item.origin_queue).push(item)
|
63
|
+
else
|
64
|
+
error_item(item)
|
75
65
|
end
|
66
|
+
end
|
76
67
|
|
77
|
-
|
78
|
-
|
79
|
-
|
68
|
+
def self.error_item(item)
|
69
|
+
item.fatal_error_at = Time.now.to_f
|
70
|
+
error_queue.push(item)
|
71
|
+
end
|
80
72
|
|
81
|
-
|
82
|
-
|
83
|
-
|
73
|
+
def self.pause(queue_name)
|
74
|
+
queue(queue_name).pause
|
75
|
+
end
|
84
76
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
77
|
+
def self.unpause(queue_name)
|
78
|
+
queue(queue_name).unpause
|
79
|
+
end
|
89
80
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
81
|
+
def self.configure(&block)
|
82
|
+
initialize_if_necessary
|
83
|
+
yield @config
|
84
|
+
end
|
94
85
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
86
|
+
def self.max_retries
|
87
|
+
initialize_if_necessary
|
88
|
+
@config.max_retries
|
89
|
+
end
|
99
90
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
91
|
+
def self.stat_timeout
|
92
|
+
initialize_if_necessary
|
93
|
+
@config.stat_timeout
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.stat_timeout_seconds
|
97
|
+
initialize_if_necessary
|
98
|
+
@config.stat_timeout * 60
|
99
|
+
end
|
104
100
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
end
|
101
|
+
def self.watch(queue_name, &block)
|
102
|
+
queue = queue(queue_name)
|
103
|
+
while(self.watch?)
|
104
|
+
item = queue.pop
|
105
|
+
fix_binding(block)
|
106
|
+
item.started_processing_at = Time.now.to_f
|
107
|
+
|
108
|
+
begin
|
109
|
+
block.call(item, self)
|
110
|
+
rescue ItemRetried
|
111
|
+
queue.add_retry_stat(item)
|
112
|
+
rescue ItemErrored
|
113
|
+
queue.add_error_stat(item)
|
114
|
+
else
|
115
|
+
item.finished_processing_at = Time.now.to_f
|
116
|
+
queue.add_success_stat(item)
|
122
117
|
end
|
123
118
|
end
|
119
|
+
end
|
124
120
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
def initialize_if_necessary
|
130
|
-
@config ||= Config.new
|
131
|
-
end
|
121
|
+
private
|
122
|
+
def self.initialize_if_necessary
|
123
|
+
@config ||= Config.new
|
124
|
+
end
|
132
125
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
126
|
+
# Hacky-hacky. I like the idea of calling retry_item(item) and
|
127
|
+
# error_item(item)
|
128
|
+
# directly from the watch block, but this seems ugly. There may be a better
|
129
|
+
# way to do this.
|
130
|
+
def self.fix_binding(block)
|
131
|
+
binding = block.binding
|
132
|
+
binding.eval <<-HERE
|
133
|
+
def retry_item(item)
|
134
|
+
Timberline.retry_item(item)
|
135
|
+
raise Timberline::ItemRetried
|
136
|
+
end
|
137
|
+
|
138
|
+
def error_item(item)
|
139
|
+
Timberline.error_item(item)
|
140
|
+
raise Timberline::ItemErrored
|
141
|
+
end
|
142
|
+
HERE
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.watch?
|
146
|
+
watch_proc.nil? ? true : watch_proc.call
|
151
147
|
end
|
152
148
|
|
153
149
|
class ItemRetried < Exception
|
@@ -156,5 +152,3 @@ class Timberline
|
|
156
152
|
class ItemErrored < Exception
|
157
153
|
end
|
158
154
|
end
|
159
|
-
|
160
|
-
Timberline.instance_variable_set("@queue_list", {})
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Timberline::Config do
|
4
|
+
describe "newly created" do
|
5
|
+
context "when the TIMBERLINE_YAML constant is defined" do
|
6
|
+
context "and the specified file exists" do
|
7
|
+
before do
|
8
|
+
SpecSupport::TimberlineYaml.load_constant
|
9
|
+
end
|
10
|
+
|
11
|
+
after do
|
12
|
+
SpecSupport::TimberlineYaml.destroy_constant
|
13
|
+
end
|
14
|
+
|
15
|
+
it "loads the host variable from the config file" do
|
16
|
+
expect(subject.host).to eq("localhost")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "loads the port variable from the config file" do
|
20
|
+
expect(subject.port).to eq(12345)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "loads the timeout variable from the config file" do
|
24
|
+
expect(subject.timeout).to eq(10)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "loads the password from the config file" do
|
28
|
+
expect(subject.password).to eq("foo")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "loads the database from the config file" do
|
32
|
+
expect(subject.database).to eq(3)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "loads the namespace from the config file" do
|
36
|
+
expect(subject.namespace).to eq("treecurve")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "and the specified file doesn't exist" do
|
41
|
+
before do
|
42
|
+
SpecSupport::TimberlineYaml.load_constant_for_missing_file
|
43
|
+
end
|
44
|
+
|
45
|
+
after do
|
46
|
+
SpecSupport::TimberlineYaml.destroy_constant
|
47
|
+
end
|
48
|
+
|
49
|
+
it "raises an exception to let you know the file doesn't exist" do
|
50
|
+
expect { Timberline::Config.new }.to raise_error
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "if Rails.root is defined" do
|
56
|
+
context "and a config/timberline.yaml file exists" do
|
57
|
+
before do
|
58
|
+
SpecSupport::FakeRails.create_fake_env
|
59
|
+
end
|
60
|
+
|
61
|
+
after do
|
62
|
+
SpecSupport::FakeRails.destroy_fake_env
|
63
|
+
end
|
64
|
+
|
65
|
+
it "loads the host variable from the config file" do
|
66
|
+
expect(subject.host).to eq("localhost")
|
67
|
+
end
|
68
|
+
|
69
|
+
it "loads the port variable from the config file" do
|
70
|
+
expect(subject.port).to eq(12345)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "loads the timeout variable from the config file" do
|
74
|
+
expect(subject.timeout).to eq(10)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "loads the password from the config file" do
|
78
|
+
expect(subject.password).to eq("foo")
|
79
|
+
end
|
80
|
+
|
81
|
+
it "loads the database from the config file" do
|
82
|
+
expect(subject.database).to eq(3)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "loads the namespace from the config file" do
|
86
|
+
expect(subject.namespace).to eq("treecurve")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "and a config/timberline.yaml file does not exist" do
|
91
|
+
before do
|
92
|
+
SpecSupport::FakeRails.create_fake_env_without_config
|
93
|
+
end
|
94
|
+
|
95
|
+
after do
|
96
|
+
SpecSupport::FakeRails.destroy_fake_env
|
97
|
+
end
|
98
|
+
|
99
|
+
it "configures a default namespace of 'timberline'" do
|
100
|
+
expect(subject.namespace).to eq("timberline")
|
101
|
+
end
|
102
|
+
|
103
|
+
it "configures a default max_retries of 5" do
|
104
|
+
expect(subject.max_retries).to eq(5)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "configures a stat_timeout of 60" do
|
108
|
+
expect(subject.stat_timeout).to eq(60)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "doesn't configure any redis settings so that redis will use its own defaults" do
|
112
|
+
[:database, :host, :port, :timeout, :password, :logger].each do |value|
|
113
|
+
expect(subject.send(value)).to be_nil
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "when no configuration exists" do
|
120
|
+
it "configures a default namespace of 'timberline'" do
|
121
|
+
expect(subject.namespace).to eq("timberline")
|
122
|
+
end
|
123
|
+
|
124
|
+
it "configures a default max_retries of 5" do
|
125
|
+
expect(subject.max_retries).to eq(5)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "configures a stat_timeout of 60" do
|
129
|
+
expect(subject.stat_timeout).to eq(60)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "doesn't configure any redis settings so that redis will use its own defaults" do
|
133
|
+
[:database, :host, :port, :timeout, :password, :logger].each do |value|
|
134
|
+
expect(subject.send(value)).to be_nil
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "#redis_config" do
|
141
|
+
subject { Timberline::Config.new }
|
142
|
+
|
143
|
+
it "returns a hash" do
|
144
|
+
expect(subject.redis_config).to be_a Hash
|
145
|
+
end
|
146
|
+
|
147
|
+
context "when only defaults have been configured" do
|
148
|
+
it "is empty" do
|
149
|
+
expect(subject.redis_config).to be_empty
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context "when it has been fully configured" do
|
154
|
+
before do
|
155
|
+
subject.database = 1
|
156
|
+
subject.host = "localhost"
|
157
|
+
subject.port = 1234
|
158
|
+
subject.timeout = 15
|
159
|
+
subject.password = "fritters"
|
160
|
+
subject.logger = Logger.new(STDOUT)
|
161
|
+
end
|
162
|
+
|
163
|
+
it "includes the appropriate redis configuration keys" do
|
164
|
+
[:db, :host, :port, :timeout, :password, :logger].each do |key|
|
165
|
+
expect(subject.redis_config).to have_key key
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Timberline::Envelope do
|
4
|
+
describe "newly created" do
|
5
|
+
it "has no contents" do
|
6
|
+
expect(subject.contents).to be_nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it "has a metadata object" do
|
10
|
+
expect(subject.metadata).not_to be_nil
|
11
|
+
end
|
12
|
+
|
13
|
+
it "has an empty hash for metadata" do
|
14
|
+
expect(subject.metadata).to eq({})
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#to_s" do
|
19
|
+
context "When the envelope has no contents" do
|
20
|
+
before do
|
21
|
+
subject.contents = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
it "raises a MissingContentException" do
|
25
|
+
expect { subject.to_s }.to raise_error(Timberline::MissingContentException)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "When the envelope has contents" do
|
30
|
+
before do
|
31
|
+
subject.contents = "Wheeee!"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "renders a JSON string of the envelope's data" do
|
35
|
+
expect(subject.to_s).to eq(JSON.unparse({contents: "Wheeee!"}))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "When the envelope has contents and metadata" do
|
40
|
+
before do
|
41
|
+
subject.contents = "Wheeee!"
|
42
|
+
subject.fritters = "the bacon kind"
|
43
|
+
end
|
44
|
+
|
45
|
+
it "renders a JSON string of the envelope's data" do
|
46
|
+
expect(subject.to_s).to eq(JSON.unparse({contents: "Wheeee!", fritters: "the bacon kind"}))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#method_missing" do
|
52
|
+
context "When the missing method has an = at the end" do
|
53
|
+
before do
|
54
|
+
subject.fritters = "the bacon kind"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "acts like a setter for the metadata object" do
|
58
|
+
expect(subject.metadata["fritters"]).to eq("the bacon kind")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "When the missing method doesn't have an = at the end" do
|
63
|
+
before do
|
64
|
+
subject.metadata["fritters"] = "the bacon kind"
|
65
|
+
end
|
66
|
+
|
67
|
+
it "acts like a getter for the metadata object" do
|
68
|
+
expect(subject.fritters).to eq("the bacon kind")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe ".from_json" do
|
74
|
+
let(:data_hash) { { contents: "hey guys!", test: true, fritters: "the bacon kind" } }
|
75
|
+
let(:json_string) { JSON.unparse(data_hash) }
|
76
|
+
let(:envelope) { Timberline::Envelope.from_json(json_string) }
|
77
|
+
|
78
|
+
it "creates a Timberline::Envelope" do
|
79
|
+
expect(envelope).to be_a Timberline::Envelope
|
80
|
+
end
|
81
|
+
|
82
|
+
it "correctly parses the contents" do
|
83
|
+
expect(envelope.contents).to eq(data_hash[:contents])
|
84
|
+
end
|
85
|
+
|
86
|
+
it "correctly parses the metadata" do
|
87
|
+
expect(envelope.test).to be true
|
88
|
+
expect(envelope.fritters).to eq "the bacon kind"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|