pantry 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +19 -0
  5. data/Gemfile +15 -0
  6. data/Guardfile +16 -0
  7. data/LICENSE +20 -0
  8. data/README.md +53 -0
  9. data/Rakefile +18 -0
  10. data/Vagrantfile +86 -0
  11. data/bin/pantry +11 -0
  12. data/bin/pantry-client +38 -0
  13. data/bin/pantry-server +33 -0
  14. data/dist/client.yml +79 -0
  15. data/dist/server.yml +56 -0
  16. data/dist/upstart/pantry-client.conf +12 -0
  17. data/dist/upstart/pantry-server.conf +12 -0
  18. data/doc/message_packet.dot +19 -0
  19. data/doc/message_packet.dot.png +0 -0
  20. data/doc/network_topology.dot +42 -0
  21. data/doc/network_topology.dot.png +0 -0
  22. data/lib/celluloid_zmq_patches.rb +16 -0
  23. data/lib/opt_parse_plus.rb +184 -0
  24. data/lib/pantry.rb +197 -0
  25. data/lib/pantry/cli.rb +154 -0
  26. data/lib/pantry/client.rb +131 -0
  27. data/lib/pantry/client_info.rb +34 -0
  28. data/lib/pantry/client_registry.rb +104 -0
  29. data/lib/pantry/command.rb +194 -0
  30. data/lib/pantry/command_handler.rb +53 -0
  31. data/lib/pantry/command_line.rb +115 -0
  32. data/lib/pantry/commands/create_client.rb +30 -0
  33. data/lib/pantry/commands/download_directory.rb +35 -0
  34. data/lib/pantry/commands/echo.rb +32 -0
  35. data/lib/pantry/commands/edit_application.rb +60 -0
  36. data/lib/pantry/commands/register_client.rb +38 -0
  37. data/lib/pantry/commands/status.rb +78 -0
  38. data/lib/pantry/commands/sync_directory.rb +50 -0
  39. data/lib/pantry/commands/update_application.rb +45 -0
  40. data/lib/pantry/commands/upload_file.rb +68 -0
  41. data/lib/pantry/communication.rb +20 -0
  42. data/lib/pantry/communication/client.rb +75 -0
  43. data/lib/pantry/communication/client_filter.rb +117 -0
  44. data/lib/pantry/communication/file_service.rb +125 -0
  45. data/lib/pantry/communication/file_service/file_progress.rb +164 -0
  46. data/lib/pantry/communication/file_service/receive_file.rb +97 -0
  47. data/lib/pantry/communication/file_service/send_file.rb +74 -0
  48. data/lib/pantry/communication/publish_socket.rb +20 -0
  49. data/lib/pantry/communication/reading_socket.rb +89 -0
  50. data/lib/pantry/communication/receive_socket.rb +23 -0
  51. data/lib/pantry/communication/security.rb +44 -0
  52. data/lib/pantry/communication/security/authentication.rb +98 -0
  53. data/lib/pantry/communication/security/curve_key_store.rb +120 -0
  54. data/lib/pantry/communication/security/curve_security.rb +70 -0
  55. data/lib/pantry/communication/security/null_security.rb +32 -0
  56. data/lib/pantry/communication/send_socket.rb +19 -0
  57. data/lib/pantry/communication/serialize_message.rb +84 -0
  58. data/lib/pantry/communication/server.rb +97 -0
  59. data/lib/pantry/communication/subscribe_socket.rb +33 -0
  60. data/lib/pantry/communication/wait_list.rb +45 -0
  61. data/lib/pantry/communication/writing_socket.rb +46 -0
  62. data/lib/pantry/config.rb +182 -0
  63. data/lib/pantry/file_editor.rb +67 -0
  64. data/lib/pantry/logger.rb +78 -0
  65. data/lib/pantry/message.rb +134 -0
  66. data/lib/pantry/multi_command.rb +36 -0
  67. data/lib/pantry/server.rb +132 -0
  68. data/lib/pantry/test/acceptance.rb +83 -0
  69. data/lib/pantry/test/support/fake_fs.rb +31 -0
  70. data/lib/pantry/test/support/matchers.rb +13 -0
  71. data/lib/pantry/test/support/minitest.rb +13 -0
  72. data/lib/pantry/test/support/mock_ui.rb +23 -0
  73. data/lib/pantry/test/unit.rb +13 -0
  74. data/lib/pantry/ui.rb +68 -0
  75. data/lib/pantry/version.rb +3 -0
  76. data/pantry.gemspec +40 -0
  77. data/test/acceptance/cli/error_handling_test.rb +7 -0
  78. data/test/acceptance/cli/execute_command_on_clients_test.rb +32 -0
  79. data/test/acceptance/cli/request_info_from_server_test.rb +44 -0
  80. data/test/acceptance/communication/client_requests_info_from_server_test.rb +28 -0
  81. data/test/acceptance/communication/heartbeat_test.rb +19 -0
  82. data/test/acceptance/communication/pub_sub_communication_test.rb +53 -0
  83. data/test/acceptance/communication/security_test.rb +117 -0
  84. data/test/acceptance/communication/server_requests_info_from_client_test.rb +41 -0
  85. data/test/acceptance/test_helper.rb +25 -0
  86. data/test/fixtures/config.yml +22 -0
  87. data/test/fixtures/empty.yml +2 -0
  88. data/test/fixtures/file_to_upload +3 -0
  89. data/test/root_dir/.gitkeep +0 -0
  90. data/test/unit/cli_test.rb +173 -0
  91. data/test/unit/client_registry_test.rb +61 -0
  92. data/test/unit/client_test.rb +128 -0
  93. data/test/unit/command_handler_test.rb +79 -0
  94. data/test/unit/command_line_test.rb +5 -0
  95. data/test/unit/command_test.rb +206 -0
  96. data/test/unit/commands/create_client_test.rb +25 -0
  97. data/test/unit/commands/download_directory_test.rb +58 -0
  98. data/test/unit/commands/echo_test.rb +22 -0
  99. data/test/unit/commands/edit_application_test.rb +84 -0
  100. data/test/unit/commands/register_client_test.rb +41 -0
  101. data/test/unit/commands/status_test.rb +81 -0
  102. data/test/unit/commands/sync_directory_test.rb +75 -0
  103. data/test/unit/commands/update_application_test.rb +35 -0
  104. data/test/unit/commands/upload_file_test.rb +51 -0
  105. data/test/unit/communication/client_filter_test.rb +262 -0
  106. data/test/unit/communication/client_test.rb +99 -0
  107. data/test/unit/communication/file_service/receive_file_test.rb +214 -0
  108. data/test/unit/communication/file_service/send_file_test.rb +110 -0
  109. data/test/unit/communication/file_service_test.rb +56 -0
  110. data/test/unit/communication/publish_socket_test.rb +19 -0
  111. data/test/unit/communication/reading_socket_test.rb +110 -0
  112. data/test/unit/communication/receive_socket_test.rb +20 -0
  113. data/test/unit/communication/security/authentication_test.rb +97 -0
  114. data/test/unit/communication/security/curve_key_store_test.rb +110 -0
  115. data/test/unit/communication/security/curve_security_test.rb +44 -0
  116. data/test/unit/communication/security/null_security_test.rb +15 -0
  117. data/test/unit/communication/security_test.rb +49 -0
  118. data/test/unit/communication/send_socket_test.rb +19 -0
  119. data/test/unit/communication/serialize_message_test.rb +128 -0
  120. data/test/unit/communication/server_test.rb +106 -0
  121. data/test/unit/communication/subscribe_socket_test.rb +46 -0
  122. data/test/unit/communication/wait_list_test.rb +60 -0
  123. data/test/unit/communication/writing_socket_test.rb +46 -0
  124. data/test/unit/config_test.rb +150 -0
  125. data/test/unit/logger_test.rb +79 -0
  126. data/test/unit/message_test.rb +179 -0
  127. data/test/unit/multi_command_test.rb +45 -0
  128. data/test/unit/opt_parse_plus_test.rb +218 -0
  129. data/test/unit/pantry_test.rb +82 -0
  130. data/test/unit/server_test.rb +166 -0
  131. data/test/unit/test_helper.rb +25 -0
  132. data/test/unit/ui_test.rb +58 -0
  133. metadata +389 -13
@@ -0,0 +1,79 @@
1
+ require 'unit/test_helper'
2
+
3
+ describe Pantry::Logger do
4
+
5
+ let(:mock_logger) {
6
+ logger = stub
7
+ logger.stubs(:level=)
8
+ logger
9
+ }
10
+
11
+ after do
12
+ # Unset global state caused by these tests
13
+ Celluloid.logger = nil
14
+ end
15
+
16
+ it "is accessible through top level Pantry.logger" do
17
+ logger = Pantry.logger = Pantry::Logger.new
18
+ assert_equal logger, Pantry.logger
19
+ end
20
+
21
+ it "sets the celluloid logger" do
22
+ logger = Pantry::Logger.new
23
+
24
+ assert Celluloid.logger.is_a?(::Logger), "Celluloid logger not set properly"
25
+ end
26
+
27
+ it "sets celluloid logger to a path if one given" do
28
+ config = Pantry::Config.new
29
+ config.log_to = File.expand_path("../../test.log", __FILE__)
30
+
31
+ logger = Pantry::Logger.new(config)
32
+
33
+ assert Celluloid.logger.is_a?(::Logger), "Celluloid logger not set properly"
34
+ end
35
+
36
+ it "sets the logger to go to Syslog if so configured" do
37
+ config = Pantry::Config.new
38
+ config.log_to = "syslog"
39
+
40
+ Syslog::Logger.expects(:new).with("pantry").returns(mock_logger)
41
+
42
+ logger = Pantry::Logger.new(config)
43
+ end
44
+
45
+ it "configures the Syslog program name if one given in the config" do
46
+ config = Pantry::Config.new
47
+ config.log_to = "syslog"
48
+ config.syslog_program_name = "pantry-client"
49
+
50
+ Syslog::Logger.expects(:new).with("pantry-client").returns(mock_logger)
51
+
52
+ logger = Pantry::Logger.new(config)
53
+ end
54
+
55
+ it "sets the log's level according to config.log_level" do
56
+ Pantry.config.log_level = :info
57
+ logger = Pantry::Logger.new
58
+
59
+ assert_equal ::Logger::INFO, Celluloid.logger.level
60
+ end
61
+
62
+ it "allows symbols when setting log level" do
63
+ config = Pantry::Config.new
64
+ config.log_level = :warn
65
+
66
+ logger = Pantry::Logger.new(config)
67
+
68
+ assert_equal ::Logger::WARN, Celluloid.logger.level
69
+ end
70
+
71
+ it "forwards unknown messages to the celluloid logger" do
72
+ logger = Pantry::Logger.new
73
+
74
+ Celluloid.logger.expects(:info).with("Message!")
75
+
76
+ logger.info("Message!")
77
+ end
78
+
79
+ end
@@ -0,0 +1,179 @@
1
+ require 'unit/test_helper'
2
+
3
+ describe Pantry::Message do
4
+
5
+ it "takes a message type in constructor" do
6
+ message = Pantry::Message.new("message_type")
7
+ assert_equal "message_type", message.type
8
+ end
9
+
10
+ it "generates a UUID on construction" do
11
+ message = Pantry::Message.new("message_type")
12
+ assert_not_nil message.uuid
13
+ assert message.uuid.length > 10
14
+ end
15
+
16
+ it "knows who the message is meant for" do
17
+ message = Pantry::Message.new("")
18
+ message.to = "stream"
19
+ assert_equal "stream", message.to
20
+ end
21
+
22
+ it "defaults the #to value to all (the empty string)" do
23
+ message = Pantry::Message.new("")
24
+ assert_equal "", message.to
25
+ end
26
+
27
+ it "can be given strings for the body parts" do
28
+ message = Pantry::Message.new("type")
29
+ message << "Part 1"
30
+ message << "Part 2"
31
+
32
+ assert_equal ["Part 1", "Part 2"], message.body
33
+ end
34
+
35
+ it "can be given the identity of the sending party" do
36
+ message = Pantry::Message.new("type")
37
+ message.from = "server1"
38
+
39
+ assert_equal "server1", message.from
40
+ end
41
+
42
+ it "can pull the identity string from an object that responds to identity" do
43
+ message = Pantry::Message.new("type")
44
+ client = Pantry::Client.new identity: "johnsonville"
45
+ message.from = client
46
+
47
+ assert_equal "johnsonville", message.from
48
+ end
49
+
50
+ it "can be flagged to require a response" do
51
+ message = Pantry::Message.new("type")
52
+ message.requires_response!
53
+
54
+ assert message.requires_response?
55
+ end
56
+
57
+ it "can build a response version of itself" do
58
+ message = Pantry::Message.new("type")
59
+ message << "Body part 1"
60
+ message << "Body part 2"
61
+ message.to = "server"
62
+ message.from = "client"
63
+ message.forwarded!
64
+ message.requires_response!
65
+
66
+ response = message.build_response
67
+
68
+ assert_equal "type", response.type
69
+ assert_equal [], response.body
70
+ assert_false response.requires_response?, "Message shouldn't require a response"
71
+ assert_equal "client", response.to
72
+ assert_equal "server", response.from
73
+ assert response.forwarded?
74
+
75
+ assert_equal message.uuid, response.uuid
76
+ end
77
+
78
+ it "clones custom metadata in a response message" do
79
+ message = Pantry::Message.new("type")
80
+ message[:custom_data] = "one"
81
+
82
+ response = message.build_response
83
+
84
+ assert_equal "one", response[:custom_data]
85
+
86
+ response[:custom_data] = "two"
87
+ assert_equal "one", message[:custom_data]
88
+ assert_equal "two", response[:custom_data]
89
+ end
90
+
91
+ it "can be flagged as being forwarded" do
92
+ message = Pantry::Message.new("type")
93
+ assert_false message.forwarded?
94
+
95
+ message.forwarded!
96
+ assert message.forwarded?
97
+ end
98
+
99
+ it "has a hash of metadata" do
100
+ message = Pantry::Message.new
101
+ message.type = "read_stuff"
102
+ message.requires_response!
103
+ message.forwarded!
104
+ message.from = "99 Luftballoons"
105
+ message.to = "streamer"
106
+
107
+ assert_not_nil message.metadata[:uuid]
108
+ assert_equal "read_stuff", message.metadata[:type]
109
+ assert_equal "99 Luftballoons", message.metadata[:from]
110
+ assert_equal "streamer", message.metadata[:to]
111
+ assert message.metadata[:requires_response]
112
+ assert message.metadata[:forwarded]
113
+ end
114
+
115
+ it "ensures the #to field is always a string when writing metadata" do
116
+ message = Pantry::Message.new
117
+ assert_equal "", message.metadata[:to]
118
+ end
119
+
120
+ it "knows if it came from the server or a client" do
121
+ message = Pantry::Message.new
122
+ message.from = Pantry::SERVER_IDENTITY
123
+
124
+ assert message.from_server?
125
+ end
126
+
127
+ it "takes a hash of metadata and parses out approriate values" do
128
+ message = Pantry::Message.new
129
+ message.metadata = {
130
+ type: "read_stuff",
131
+ requires_response: true,
132
+ forwarded: true,
133
+ from: "99 Luftballoons",
134
+ to: "streamer",
135
+ uuid: "123-4567-890-1234"
136
+ }
137
+
138
+ assert_equal "123-4567-890-1234", message.uuid
139
+ assert_equal "read_stuff", message.type
140
+ assert_equal "99 Luftballoons", message.from
141
+ assert_equal "streamer", message.to
142
+ assert message.requires_response?
143
+ assert message.forwarded?
144
+ end
145
+
146
+ it "ensures the #to field is always a string when reading metadata" do
147
+ message = Pantry::Message.new
148
+ message.metadata = {
149
+ to: nil,
150
+ }
151
+ assert_equal "", message.to
152
+ end
153
+
154
+ it "allows custom metadata entries" do
155
+ message = Pantry::Message.new
156
+ message[:metadata_1] = "my metadata"
157
+ message[:some_name] = "johnson"
158
+
159
+ assert_equal "my metadata", message[:metadata_1]
160
+ assert_equal "johnson", message[:some_name]
161
+
162
+ assert_equal "my metadata", message.metadata[:custom][:metadata_1]
163
+ assert_equal "johnson", message.metadata[:custom][:some_name]
164
+ end
165
+
166
+ it "loads unknown metadata keys into the custom metadata" do
167
+ message = Pantry::Message.new
168
+ message.metadata = {
169
+ custom: {
170
+ metadata_1: "my metadata",
171
+ some_name: "johnson"
172
+ }
173
+ }
174
+
175
+ assert_equal "my metadata", message[:metadata_1]
176
+ assert_equal "johnson", message[:some_name]
177
+ end
178
+
179
+ end
@@ -0,0 +1,45 @@
1
+ require 'unit/test_helper'
2
+
3
+ describe Pantry::MultiCommand do
4
+
5
+ class TestPart1 < Pantry::Command
6
+ def perform(message)
7
+ [1, client.identity, message.type]
8
+ end
9
+ end
10
+
11
+ class TestPart2 < Pantry::Command
12
+ def perform(message)
13
+ [2, client.identity, message.type]
14
+ end
15
+ end
16
+
17
+ class TestPart3 < Pantry::Command
18
+ def perform(message)
19
+ [3, client.identity, message.type]
20
+ end
21
+ end
22
+
23
+ class TestMultiCommand < Pantry::MultiCommand
24
+ performs [
25
+ TestPart1,
26
+ TestPart2,
27
+ TestPart3
28
+ ]
29
+ end
30
+
31
+ it "runs multiple commands in order, combining return values" do
32
+ client = Pantry::Client.new identity: "Client"
33
+ command = TestMultiCommand.new
34
+ command.client = client
35
+
36
+ results = command.perform(Pantry::Message.new("Message"))
37
+
38
+ assert_equal [
39
+ [1, "Client", "Message"],
40
+ [2, "Client", "Message"],
41
+ [3, "Client", "Message"],
42
+ ], results
43
+ end
44
+
45
+ end
@@ -0,0 +1,218 @@
1
+ require 'unit/test_helper'
2
+
3
+ describe OptParsePlus do
4
+
5
+ it "adds block and option support to optparse" do
6
+ parser = OptParsePlus.new
7
+ parser.add_options do
8
+ option "-a", "--application APPLICATION", String, "An option"
9
+ end
10
+
11
+ command_line = %w(-a application_name)
12
+ options = parser.parse!(command_line)
13
+
14
+ assert_equal "application_name", options['application']
15
+ assert_equal "application_name", options[:application]
16
+ assert_equal [], command_line
17
+ end
18
+
19
+ it "adds command support to optparse" do
20
+ parser = OptParsePlus.new
21
+ parser.add_command("one") do
22
+ option "-a", "--application APPLICATION", String, "An option"
23
+ end
24
+
25
+ command_line = %w(one -a application_name)
26
+ options = parser.parse!(command_line)
27
+
28
+ assert_equal "application_name", options['one']['application']
29
+ assert_equal "application_name", options[:one][:application]
30
+ assert_equal [], command_line
31
+ end
32
+
33
+ it "allows setting an explicit banner" do
34
+ parser = OptParsePlus.new
35
+ parser.banner "pantry [command]"
36
+
37
+ help_text = parser.help
38
+ assert_match /pantry \[command\]/, help_text
39
+ end
40
+
41
+ it "allows specifying a description" do
42
+ parser = OptParsePlus.new
43
+ parser.add_options do
44
+ description "This is interesting yes!"
45
+ end
46
+
47
+ help_text = parser.help
48
+ assert_match /This is interesting/, help_text
49
+ end
50
+
51
+ it "works with commands with no block" do
52
+ parser = OptParsePlus.new
53
+ parser.add_command("run")
54
+
55
+ options = parser.parse!(%w(run))
56
+ assert_equal "run", options.command_found
57
+ end
58
+
59
+ it "allows specifying argument usage in the command name" do
60
+ parser = OptParsePlus.new
61
+ parser.add_command("run COMMAND")
62
+
63
+ command_line = %w(run Something)
64
+ options = parser.parse!(command_line)
65
+
66
+ assert_equal "run", options.command_found
67
+ assert_equal %w(Something), command_line
68
+ end
69
+
70
+ it "gives the command options the arguments as per the description string (???)"
71
+
72
+ it "processes arguments in order to support global and command options" do
73
+ parser = OptParsePlus.new
74
+ parser.add_options do
75
+ option "-L", "--log-level LEVEL"
76
+ end
77
+
78
+ parser.add_command("run") do
79
+ option "-c", "--command COMMAND"
80
+ end
81
+
82
+ command_line = %w(-L debug run --command DoThisNow)
83
+ options = parser.parse!(command_line)
84
+
85
+ assert_equal "run", options.command_found
86
+ assert_equal "debug", options['log-level']
87
+ assert_equal "DoThisNow", options['run']['command']
88
+ end
89
+
90
+ it "groups commands together, sorting subcommands alphabetically" do
91
+ parser = OptParsePlus.new
92
+ parser.add_command("run") do
93
+ group "Exercise"
94
+ end
95
+
96
+ parser.add_command("bike") do
97
+ group "Exercise"
98
+ end
99
+
100
+ parser.add_command("sleep") do
101
+ group "Lazy"
102
+ end
103
+
104
+ output, err = capture_io do
105
+ parser.parse!(%w(--help))
106
+ end
107
+
108
+ # Group subheadings
109
+ assert_match /Exercise commands/, output
110
+ assert_match /Lazy commands/, output
111
+
112
+ # Check alphabetical sort
113
+ assert_match /bike.*run/m, output
114
+ end
115
+
116
+ it "adds a global help option" do
117
+ parser = OptParsePlus.new
118
+
119
+ options = nil
120
+ output, err = capture_io do
121
+ options = parser.parse!(%w(-h))
122
+ end
123
+
124
+ assert_match /Show this help message/, output
125
+ assert options['help'], "Help flag was not set"
126
+ end
127
+
128
+ it "adds help option to each command configured" do
129
+ parser = OptParsePlus.new
130
+ parser.add_command("run")
131
+
132
+ options = nil
133
+ output, err = capture_io do
134
+ options = parser.parse!(%w(run --help))
135
+ end
136
+
137
+ assert_match /Show this help message/, output
138
+ assert options['help']
139
+ end
140
+
141
+ it "allows explicitly getting the help text" do
142
+ parser = OptParsePlus.new
143
+ help_text = parser.help
144
+
145
+ assert_match /Show this help message/, help_text
146
+ end
147
+
148
+ it "includes all known commands in the top-level help text" do
149
+ parser = OptParsePlus.new
150
+ parser.add_command("run") do
151
+ description "Run something"
152
+ end
153
+
154
+ parser.add_command("stop") do
155
+ description "Stop something from running"
156
+ end
157
+
158
+ parser.add_command("stock ITEM") do
159
+ description "Stock the item"
160
+ end
161
+
162
+ help_text = parser.help
163
+
164
+ assert_match /run\s+Run something/, help_text
165
+ assert_match /stop\s+Stop something/, help_text
166
+ assert_match /stock\s+Stock the item/, help_text
167
+ end
168
+
169
+ it "only shows the first line of a command's description in top-level help" do
170
+ parser = OptParsePlus.new
171
+ parser.add_command("run") do
172
+ description "Run something.
173
+ Run fast.
174
+ Don't run slow"
175
+ end
176
+
177
+ help_text = parser.help
178
+
179
+ assert_match /Run something/, help_text
180
+ assert_no_match /Run fast/, help_text
181
+ assert_no_match /Don't run slow/, help_text
182
+ end
183
+
184
+ it "includes sub-command descriptions and banner strings in help text" do
185
+ parser = OptParsePlus.new
186
+ parser.add_command("display MESSAGE") do
187
+ description "Show the given message"
188
+ option "-c", "--cap", "Capitalize the message"
189
+ end
190
+
191
+ help_text, error = capture_io do
192
+ parser.parse!(["display", "--help"])
193
+ end
194
+
195
+ assert_match /display \[options\] MESSAGE/, help_text
196
+ assert_match /-c, --cap/, help_text
197
+ assert_match /Capitalize the message/, help_text
198
+ assert_match /Show the given message/, help_text
199
+ end
200
+
201
+ it "cleans up extra white space in full command descriptions" do
202
+ parser = OptParsePlus.new
203
+ parser.add_command("display MESSAGE") do
204
+ description "Show the given message.
205
+ Requires a MESSAGE to be passed in.
206
+ and will Error out otherwise"
207
+ end
208
+
209
+ help_text, error = capture_io do
210
+ parser.parse!(["display", "--help"])
211
+ end
212
+
213
+ assert_match /^Show the given message/, help_text
214
+ assert_match /^Requires a MESSAGE/, help_text
215
+ assert_match /^and will Error/, help_text
216
+ end
217
+
218
+ end