revenc 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/.document +5 -0
  2. data/.gitattributes +1 -0
  3. data/.gitignore +30 -0
  4. data/CLONING.rdoc +108 -0
  5. data/LICENSE +20 -0
  6. data/README.rdoc +198 -0
  7. data/Rakefile +64 -0
  8. data/VERSION +1 -0
  9. data/bin/revenc +81 -0
  10. data/config/cucumber.yml +7 -0
  11. data/examples/rsync/encrypted_data/key/encfs6.xml +35 -0
  12. data/examples/rsync/revenc.conf +18 -0
  13. data/examples/rsync/scripts/passphrase +1 -0
  14. data/examples/rsync/unencrypted_data/test_file1.txt +1 -0
  15. data/examples/rsync/unencrypted_data/test_file2.txt +1 -0
  16. data/examples/simple/encfs6.xml +35 -0
  17. data/examples/simple/passphrase +1 -0
  18. data/examples/simple/unencrypted_data/test_file1.txt +1 -0
  19. data/examples/simple/unencrypted_data/test_file2.txt +1 -0
  20. data/features/app.feature +59 -0
  21. data/features/bin.feature +35 -0
  22. data/features/configuration.feature +98 -0
  23. data/features/copy.feature +169 -0
  24. data/features/generator.feature +15 -0
  25. data/features/mount.feature +133 -0
  26. data/features/step_definitions/.gitignore +0 -0
  27. data/features/step_definitions/revenc_steps.rb +64 -0
  28. data/features/support/aruba.rb +21 -0
  29. data/features/support/env.rb +4 -0
  30. data/features/support/hooks.rb +6 -0
  31. data/features/unmount.feature +58 -0
  32. data/lib/revenc/app.rb +128 -0
  33. data/lib/revenc/encfs_wrapper.rb +96 -0
  34. data/lib/revenc/errors.rb +78 -0
  35. data/lib/revenc/io.rb +265 -0
  36. data/lib/revenc/lockfile.rb +66 -0
  37. data/lib/revenc.rb +22 -0
  38. data/spec/revenc/error_spec.rb +50 -0
  39. data/spec/revenc/io_spec.rb +185 -0
  40. data/spec/revenc/lockfile_spec.rb +44 -0
  41. data/spec/spec.opts +2 -0
  42. data/spec/spec_helper.rb +16 -0
  43. data/spec/watchr.rb +142 -0
  44. metadata +179 -0
data/lib/revenc/app.rb ADDED
@@ -0,0 +1,128 @@
1
+ require 'configatron'
2
+ require 'term/ansicolor'
3
+
4
+ class String
5
+ include Term::ANSIColor
6
+ end
7
+
8
+ module Revenc
9
+
10
+ AVAILABLE_ACTIONS = %w[mount unmount copy]
11
+
12
+ class App
13
+
14
+ def initialize(base_dir, options={})
15
+ @base_dir = base_dir
16
+ @options = options
17
+ if @options[:verbose]
18
+ puts "base_dir: #{@base_dir}".cyan
19
+ puts "options: #{@options.inspect}".cyan
20
+ end
21
+ configure(options)
22
+ end
23
+
24
+ def run
25
+ begin
26
+
27
+ if action_argument_required?
28
+ action = ARGV.shift
29
+ unless AVAILABLE_ACTIONS.include?(action)
30
+ if action.nil?
31
+ puts "revenc action required"
32
+ else
33
+ puts "revenc invalid action: #{action}"
34
+ end
35
+ puts "revenc --help for more information"
36
+ exit 1
37
+ end
38
+ puts "revenc run action: #{action}".cyan if @options[:verbose]
39
+ raise "action #{action} not implemented" unless respond_to?(action)
40
+ result = send(action)
41
+ else
42
+ #
43
+ # default action if action_argument_required? is false
44
+ #
45
+ result = 0
46
+ end
47
+
48
+ exit(result ? 0 : 1)
49
+
50
+ rescue SystemExit => e
51
+ # This is the normal exit point, exit code from the send result
52
+ # or exit from another point in the system
53
+ puts "revenc run system exit: #{e}, status code: #{e.status}".green if @options[:verbose]
54
+ exit(e.status)
55
+ rescue Exception => e
56
+ STDERR.puts("revenc command failed, error(s) follow:")
57
+ STDERR.puts("#{e.message}".red)
58
+ STDERR.puts(e.backtrace.join("\n")) if @options[:verbose]
59
+ exit(1)
60
+ end
61
+ end
62
+
63
+ #
64
+ # app commands start
65
+ #
66
+ # TODO: Add status command, use encfsctl
67
+
68
+ def mount
69
+ EncfsWrapper.new(@base_dir, @options).mount(ARGV.shift, ARGV.shift)
70
+ end
71
+
72
+ def unmount
73
+ EncfsWrapper.new(@base_dir, @options).unmount(ARGV.shift)
74
+ end
75
+
76
+ def copy
77
+ EncfsWrapper.new(@base_dir, @options).copy(ARGV.shift, ARGV.shift)
78
+ end
79
+
80
+ #
81
+ # app commands end
82
+ #
83
+
84
+ private
85
+
86
+ # true if application requires an action to be specified on the command line
87
+ def action_argument_required?
88
+ !AVAILABLE_ACTIONS.empty?
89
+ end
90
+
91
+ # read options for YAML config with ERB processing and initialize configatron
92
+ def configure(options)
93
+ # TODO: read ~/.revenc.conf before looking in the current folder for revenc.conf, read BOTH files
94
+ config = @options[:config]
95
+ config = File.join(@base_dir, 'revenc.conf') unless config
96
+ if File.exists?(config)
97
+ # load configatron options from the config file
98
+ puts "loading config file: #{config}".cyan if @options[:verbose]
99
+ configatron.configure_from_yaml(config)
100
+ else
101
+ # user specified a config file?
102
+ raise "config file not found" if @options[:config]
103
+ # no error if user did not specify config file
104
+ puts "#{config} not found".yellow if @options[:verbose]
105
+ end
106
+
107
+ #
108
+ # set defaults, these will NOT override setting read from YAML
109
+ #
110
+ configatron.mount.source.set_default(:name, nil)
111
+ configatron.mount.mountpoint.set_default(:name, nil)
112
+ configatron.mount.passphrasefile.set_default(:name, 'passphrase')
113
+ configatron.mount.keyfile.set_default(:name, 'encfs6.xml')
114
+ configatron.mount.set_default(:cmd, nil)
115
+ configatron.mount.set_default(:executable, nil)
116
+
117
+ configatron.unmount.mountpoint.set_default(:name, nil)
118
+ configatron.unmount.set_default(:cmd, nil)
119
+ configatron.unmount.set_default(:executable, nil)
120
+
121
+ configatron.copy.source.set_default(:name, nil)
122
+ configatron.copy.destination.set_default(:name, nil)
123
+ configatron.copy.set_default(:cmd, nil)
124
+ configatron.copy.set_default(:executable, nil)
125
+ end
126
+
127
+ end
128
+ end
@@ -0,0 +1,96 @@
1
+ module Revenc
2
+
3
+ class EncfsWrapper
4
+
5
+ def initialize(base_dir, options)
6
+ raise ArgumentError, "Options should be a hash" unless options.is_a?(Hash)
7
+ @base_dir = base_dir
8
+ @options = options
9
+ end
10
+
11
+ def mount(source=nil, mount_point_folder=nil)
12
+
13
+ # add params from config file if not specified
14
+ source = configatron.mount.source.name unless source
15
+ mount_point_folder = configatron.mount.mountpoint.name unless mount_point_folder
16
+
17
+ # sanity check params
18
+ raise "source folder not specified" unless source
19
+ raise "mountpoint not specified" unless mount_point_folder
20
+
21
+ mount_point_options = @options.merge(:passphrasefile => configatron.mount.passphrasefile.name)
22
+ mount_point_options = mount_point_options.merge(:keyfile => configatron.mount.keyfile.name)
23
+ mount_point_options = mount_point_options.merge(:cmd => configatron.mount.cmd) if configatron.mount.cmd
24
+ mount_point_options = mount_point_options.merge(:executable => configatron.mount.executable) if configatron.mount.executable
25
+
26
+ mount_point = MountPoint.new(mount_point_folder, source, mount_point_options)
27
+
28
+ if @options[:verbose]
29
+ puts "mount: source=#{mount_point.source.name}".cyan
30
+ puts "mount: mountpoint=#{mount_point.name}".cyan
31
+ puts "mount: passphrasefile=#{mount_point.passphrasefile.name}".cyan
32
+ puts "mount: keyfile=#{mount_point.keyfile.name}".cyan
33
+ puts "mount: cmd=#{mount_point.cmd}".cyan
34
+ puts "mount: executable=#{mount_point.executable}".cyan
35
+ end
36
+
37
+ mount_point.execute
38
+ end
39
+
40
+ def unmount(foldername = nil)
41
+
42
+ # add param from config file if not specified, try specific unmount
43
+ foldername = configatron.unmount.mountpoint.name unless foldername
44
+ # fallback to mount.mountpoint if specified
45
+ foldername = configatron.mount.mountpoint.name unless foldername
46
+
47
+ # sanity check params
48
+ raise "mountpoint not specified" unless foldername
49
+
50
+ unmount_point_options = @options || {}
51
+ unmount_point_options = unmount_point_options.merge(:cmd => configatron.unmount.cmd) if configatron.umount.cmd
52
+ unmount_point_options = unmount_point_options.merge(:executable => configatron.unmount.executable) if configatron.umount.executable
53
+ unmount_point = UnmountPoint.new(foldername, unmount_point_options)
54
+
55
+ if @options[:verbose]
56
+ puts "unmount: mountpoint=#{unmount_point.mountpoint.name}".cyan
57
+ puts "unmount: cmd=#{unmount_point.cmd}".cyan
58
+ puts "unmount: executable=#{unmount_point.executable}".cyan
59
+ end
60
+
61
+ unmount_point.execute
62
+ end
63
+
64
+ def copy(source=nil, destination=nil)
65
+
66
+ # add params from config file if not specified
67
+ source = configatron.copy.source.name unless source
68
+ # fallback
69
+ source = configatron.mount.mountpoint.name unless source
70
+ destination = configatron.copy.destination.name unless destination
71
+
72
+ # sanity check params
73
+ raise "source folder not specified" unless source
74
+ raise "destination not specified" unless destination
75
+
76
+ copy_options = @options || {}
77
+ copy_options = copy_options.merge(:cmd => configatron.copy.cmd) if configatron.copy.cmd
78
+ copy_options = copy_options.merge(:executable => configatron.copy.executable) if configatron.copy.executable
79
+ copy_options = copy_options.merge(:mountpoint => configatron.mount.mountpoint.name) if configatron.mount.mountpoint.name
80
+
81
+ copy_folder = CopySourceFolder.new( source, destination, copy_options)
82
+
83
+ if @options[:verbose]
84
+ puts "copy: source=#{copy_folder.name}".cyan
85
+ puts "copy: destination=#{copy_folder.destination.name}".cyan
86
+ puts "copy: cmd=#{copy_folder.cmd}".cyan
87
+ puts "copy: executable=#{copy_folder.executable}".cyan
88
+ end
89
+
90
+ copy_folder.execute
91
+ end
92
+
93
+ end
94
+
95
+ end
96
+
@@ -0,0 +1,78 @@
1
+ module Revenc
2
+
3
+ class Errors
4
+ include Enumerable
5
+
6
+ def initialize
7
+ @errors ={}
8
+ end
9
+
10
+ # add errors, error_on can be a symbol or object instance
11
+ def add(error_on, message = "Unknown error")
12
+
13
+ # humanize error_on
14
+ if error_on.is_a?(Symbol)
15
+ error_on_str = error_on.to_s
16
+ else
17
+ error_on_str = underscore(error_on.class.name)
18
+ end
19
+ error_on_str = error_on_str.gsub(/\//, '_')
20
+ error_on_str = error_on_str.gsub(/_/, ' ')
21
+ error_on_str = error_on_str.gsub(/^revenc/, '').strip
22
+ #error_on_str = error_on_str.capitalize
23
+
24
+ @errors[error_on_str] ||= []
25
+ @errors[error_on_str] << message.to_s
26
+ end
27
+
28
+ def empty?
29
+ @errors.empty?
30
+ end
31
+
32
+ def clear
33
+ @errors = {}
34
+ end
35
+
36
+ def each
37
+ @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
38
+ end
39
+
40
+ def size
41
+ @errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
42
+ end
43
+
44
+ alias_method :count, :size
45
+ alias_method :length, :size
46
+
47
+ def messages
48
+ messages = []
49
+
50
+ @errors.each_key do |attr|
51
+ @errors[attr].each do |message|
52
+ next unless message
53
+ attr_name = attr.to_s
54
+ messages << attr_name + ' ' + message
55
+ end
56
+ end
57
+
58
+ messages
59
+ end
60
+
61
+ def to_sentences
62
+ messages.join("\n")
63
+ end
64
+
65
+ private
66
+
67
+ # from ActiveSupport
68
+ def underscore(camel_cased_word)
69
+ camel_cased_word.to_s.gsub(/::/, '/').
70
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
71
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
72
+ tr("-", "_").
73
+ downcase
74
+ end
75
+
76
+ end
77
+
78
+ end
data/lib/revenc/io.rb ADDED
@@ -0,0 +1,265 @@
1
+ module Revenc
2
+
3
+ class BasicAction
4
+
5
+ def initialize(name=nil, options={})
6
+ raise ArgumentError, "Options should be a hash" unless options.is_a?(Hash)
7
+ @name = name
8
+ @options = options
9
+ end
10
+
11
+ def name
12
+ @name
13
+ end
14
+
15
+ def name=(value)
16
+ @name = value
17
+ end
18
+
19
+ def options
20
+ @options ||= {}
21
+ end
22
+
23
+ def options=(value)
24
+ @options = value
25
+ end
26
+
27
+ def errors
28
+ @errors ||= Errors.new
29
+ end
30
+
31
+ def exists?
32
+ false
33
+ end
34
+
35
+ def validate
36
+ end
37
+
38
+ def valid?
39
+ errors.clear
40
+ validate
41
+ errors.empty?
42
+ end
43
+
44
+ def cmd
45
+ return nil unless @cmd
46
+ # process ERB
47
+ render(@cmd)
48
+ end
49
+
50
+ def cmd=(value)
51
+ @cmd = value
52
+ end
53
+
54
+ private
55
+
56
+ def system_cmd(cmd=nil)
57
+ raise "ERROR: cmd not given" unless cmd
58
+ return true if options[:dry_run]
59
+ system cmd
60
+ end
61
+
62
+ # Runs the YAML file through ERB
63
+ def render(value, b = binding)
64
+ ERB.new(value).result(b)
65
+ end
66
+
67
+ end
68
+
69
+ class FileSystemEntity < BasicAction
70
+
71
+ # return fully qualified name
72
+ def name
73
+ return @name unless (@name && File.exists?(@name))
74
+ File.expand_path(@name)
75
+ end
76
+
77
+ def exists?
78
+ File.exists?(name) if name
79
+ end
80
+
81
+ end
82
+
83
+ class TextFile < FileSystemEntity
84
+
85
+
86
+ def empty?
87
+ return true unless exists?
88
+
89
+ contents = nil
90
+ File.open(@name, "r") do |f|
91
+ contents = f.read
92
+ end
93
+ contents.empty?
94
+ end
95
+
96
+ def validate
97
+ errors.add(self, "filename not specified") if @name.nil?
98
+ errors.add(self, "not found") unless exists?
99
+ end
100
+ end
101
+
102
+ class PassphraseFile < TextFile
103
+
104
+ def initialize(name='passphrase', options={})
105
+ super name, options
106
+ end
107
+
108
+ def validate
109
+ super
110
+ errors.add(self, "is empty") if empty?
111
+ end
112
+ end
113
+
114
+ class KeyFile < TextFile
115
+
116
+ def initialize(name='encfs6.xml', options={})
117
+ super name, options
118
+ end
119
+
120
+ def validate
121
+ super
122
+ errors.add(self, "is empty") if exists? && empty?
123
+ end
124
+ end
125
+
126
+ class FileFolder < FileSystemEntity
127
+
128
+ def exists?
129
+ !@name.nil? && File.directory?(@name)
130
+ end
131
+
132
+ def empty?
133
+ return true unless exists?
134
+ Dir.entries(@name).sort == [".", ".."].sort
135
+ end
136
+
137
+ def validate
138
+ errors.add(self, "not found") unless exists?
139
+ end
140
+ end
141
+
142
+ class SourceFolder < FileFolder
143
+
144
+ def validate
145
+ super
146
+ errors.add(self, "is empty") if exists? && empty?
147
+ end
148
+ end
149
+
150
+ class ActionFolder < FileFolder
151
+ attr_accessor :passphrasefile
152
+ attr_accessor :keyfile
153
+
154
+ def initialize(name=nil, options={})
155
+ super
156
+ @passphrasefile = PassphraseFile.new(options[:passphrasefile])
157
+ @keyfile = KeyFile.new(options[:keyfile])
158
+ @cmd = options[:cmd]
159
+ @executable = options[:executable]
160
+ end
161
+
162
+ def validate
163
+ super
164
+ errors.add(self, "executable filename not specified") unless @executable
165
+ errors.add(self, "#{@executable} executable not found") if executable.empty?
166
+ errors.add(self, "cmd not specified") unless cmd
167
+ end
168
+
169
+ def executable
170
+ return nil unless @executable
171
+ result = `which #{@executable}`
172
+ result.strip
173
+ end
174
+
175
+ # run the action if valid and return true if successful
176
+ def execute
177
+ raise errors.to_sentences unless valid?
178
+
179
+ # default failing command
180
+ result = false
181
+
182
+ # protect command from recursion
183
+ mutex = Revenc::Mutex.new
184
+ lock_sucessful = mutex.execute do
185
+ result = system_cmd(cmd)
186
+ end
187
+
188
+ raise "action failed, lock file present" unless lock_sucessful
189
+ result
190
+ end
191
+ end
192
+
193
+ class MountPoint < ActionFolder
194
+ attr_accessor :source
195
+
196
+ def initialize(name=nil, source_folder_name=nil, options={})
197
+ super name, options
198
+ @source = SourceFolder.new(source_folder_name)
199
+ @cmd = options[:cmd] || "cat <%= passphrasefile.name %> | ENCFS6_CONFIG=<%= keyfile.name %> \
200
+ <%= executable %> --stdinpass --reverse <%= source.name %> <%= mountpoint.name %> -- -o ro"
201
+ @executable = options[:executable] || 'encfs'
202
+ end
203
+
204
+ # allow clarity in config files, instead of <%= name %> you can use <%= mountpoint.name %>
205
+ def mountpoint
206
+ self
207
+ end
208
+
209
+ def validate
210
+ super
211
+ errors.add(self, "is not empty") unless empty?
212
+ errors.add(self, source.errors.to_sentences) unless source.valid?
213
+ errors.add(self, keyfile.errors.to_sentences) unless keyfile.valid?
214
+ errors.add(self, passphrasefile.errors.to_sentences) unless passphrasefile.valid?
215
+ end
216
+
217
+ end
218
+
219
+ class UnmountPoint < ActionFolder
220
+
221
+ def initialize(name=nil, options={})
222
+ super
223
+ @cmd = options[:cmd] || "<%= executable %> -u <%= mountpoint.name %>"
224
+ @executable = options[:executable] || 'fusermount'
225
+ end
226
+
227
+ # allow clarity in config files, instead of <%= name %> you can use <%= mountpoint.name %>
228
+ def mountpoint
229
+ self
230
+ end
231
+
232
+ end
233
+
234
+ class DestinationPoint < BasicAction
235
+ end
236
+
237
+ class CopySourceFolder < ActionFolder
238
+ attr_accessor :destination
239
+ attr_accessor :mountpoint
240
+
241
+ def initialize(name=nil, destination_name=nil, options={})
242
+ super name, options
243
+ @destination = DestinationPoint.new(destination_name)
244
+ @mountpoint = MountPoint.new(options[:mountpoint])
245
+ @cmd = options[:cmd] || "<%= executable %> -r <%= source.name %> <%= destination.name %>"
246
+ @executable = options[:executable] || 'cp'
247
+ end
248
+
249
+ # allow clarity in config files, instead of <%= name %> you can use <%= source.name %>
250
+ def source
251
+ self
252
+ end
253
+
254
+ def validate
255
+ super
256
+ errors.add(self, "is empty") if exists? && empty?
257
+ errors.add(self, "mountpoint not found") if (mountpoint.name && !mountpoint.exists?)
258
+ errors.add(self, "mountpoint is empty") if (mountpoint.name && mountpoint.exists? && mountpoint.empty?)
259
+ errors.add(self, destination.errors.to_sentences) unless destination.valid?
260
+ end
261
+
262
+ end
263
+
264
+ end
265
+
@@ -0,0 +1,66 @@
1
+ module Revenc
2
+
3
+ class Lockfile
4
+ attr_accessor :lockfile
5
+
6
+ def initialize(lockfile=nil)
7
+ raise ArgumentError, "lockfile not specified" unless lockfile
8
+ @lockfile = lockfile
9
+ end
10
+
11
+ def locked?
12
+ return false unless File.exists?(lockfile)
13
+ result = false
14
+ open(lockfile, 'w') do |f|
15
+ # exclusive non-blocking lock
16
+ result = !lock(f, File::LOCK_EX | File::LOCK_NB)
17
+ end
18
+ result
19
+ end
20
+
21
+ # flock, get a file lock
22
+ #
23
+ # Typical usage:
24
+ #
25
+ # open('output', 'w') do |f|
26
+ # flock(f, File::LOCK_EX) do |f|
27
+ # f << "write to file"
28
+ # end
29
+ # end
30
+ def lock(file, mode)
31
+ result = file.flock(mode)
32
+ if result
33
+ begin
34
+ yield file if block_given?
35
+ ensure
36
+ file.flock(File::LOCK_UN)
37
+ end
38
+ end
39
+ return result
40
+ end
41
+ end
42
+
43
+ class Mutex < Lockfile
44
+
45
+ def initialize(lockfile='revenc.lck')
46
+ super lockfile
47
+ end
48
+
49
+ def execute
50
+ result = false
51
+ begin
52
+ open(@lockfile, 'w') do |f|
53
+ # exclusive non-blocking lock
54
+ result = lock(f, File::LOCK_EX | File::LOCK_NB) do |f|
55
+ yield if block_given?
56
+ end
57
+ end
58
+ ensure
59
+ # clean up but only if we have a positive result meaning we wrote the lockfile
60
+ FileUtils.rm(@lockfile) if (result && File.exists?(@lockfile))
61
+ end
62
+ result
63
+ end
64
+ end
65
+
66
+ end
data/lib/revenc.rb ADDED
@@ -0,0 +1,22 @@
1
+ $:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
2
+
3
+ require 'rubygems'
4
+
5
+ module Revenc
6
+
7
+ # return the contents of the VERSION file
8
+ # VERSION format: 0.0.0
9
+ def self.version
10
+ version_info_file = File.join(File.dirname(__FILE__), *%w[.. VERSION])
11
+ File.open(version_info_file, "r") do |f|
12
+ f.read
13
+ end
14
+ end
15
+
16
+ end
17
+
18
+ require 'revenc/app'
19
+ require 'revenc/io'
20
+ require 'revenc/errors'
21
+ require 'revenc/lockfile'
22
+ require 'revenc/encfs_wrapper'
@@ -0,0 +1,50 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Revenc::Errors do
4
+
5
+ before(:each) do
6
+ @error_obj = Revenc::Errors.new
7
+ end
8
+
9
+ it "should be empty when created" do
10
+ @error_obj.should be_empty
11
+ end
12
+
13
+ it "should return a count of the errors" do
14
+ @error_obj.should be_empty
15
+ @error_obj.add(:test_error1)
16
+ @error_obj.add(:test_error2)
17
+ @error_obj.should_not be_empty
18
+ @error_obj.size.should == 2
19
+ end
20
+
21
+ it "should clear the errors" do
22
+ @error_obj.should be_empty
23
+ @error_obj.add(:test_error1)
24
+ @error_obj.add(:test_error2)
25
+ @error_obj.should_not be_empty
26
+ @error_obj.size.should be(2)
27
+ @error_obj.clear
28
+ @error_obj.should be_empty
29
+ @error_obj.size.should be(0)
30
+ end
31
+
32
+ it "should return the errors in full sentences for errors on symbol" do
33
+ @error_obj.should be_empty
34
+ @error_obj.add(:test_error1, "Error no 1")
35
+ @error_obj.add(:test_error2, "Error no 2")
36
+ @error_obj.size.should be(2)
37
+ @error_obj.to_sentences.should == "test error1 Error no 1\ntest error2 Error no 2"
38
+ end
39
+
40
+ it "should return the errors in full sentences for errors on class names" do
41
+ @error_obj.should be_empty
42
+ @error_obj.add(@error_obj, "error no 1")
43
+ @error_obj.add(@error_obj, "error no 2")
44
+ @error_obj.size.should be(2)
45
+ @error_obj.to_sentences.should == "errors error no 1\nerrors error no 2"
46
+ end
47
+
48
+ end
49
+
50
+