morale 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +19 -0
- data/Gemfile.lock +56 -0
- data/LICENSE +0 -0
- data/README.md +38 -0
- data/Rakefile +150 -0
- data/bin/morale +8 -0
- data/features/accounts.feature +88 -0
- data/features/login.feature +65 -0
- data/features/projects.feature +110 -0
- data/features/step_definitions/models.rb +3 -0
- data/features/support/env.rb +6 -0
- data/features/support/hooks.rb +12 -0
- data/features/tickets.feature +26 -0
- data/lib/morale/account.rb +119 -0
- data/lib/morale/authorization.rb +20 -0
- data/lib/morale/client.rb +76 -0
- data/lib/morale/command.rb +70 -0
- data/lib/morale/commands/account.rb +61 -0
- data/lib/morale/commands/authorization.rb +13 -0
- data/lib/morale/commands/project.rb +63 -0
- data/lib/morale/commands/ticket.rb +36 -0
- data/lib/morale/credentials_store.rb +41 -0
- data/lib/morale/flow.rb +17 -0
- data/lib/morale/platform.rb +64 -0
- data/lib/morale.rb +5 -0
- data/morale.gemspec +70 -0
- data/spec/morale/account_spec.rb +11 -0
- data/spec/morale/client_spec.rb +92 -0
- data/spec/morale/command_spec.rb +67 -0
- data/spec/morale/credentials_store_spec.rb +46 -0
- data/spec/spec_helper.rb +21 -0
- metadata +148 -0
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source 'http://rubygems.org'
|
2
|
+
|
3
|
+
gem 'rake', '0.8.7'
|
4
|
+
gem 'httparty', '0.7.8'
|
5
|
+
gem 'json', '1.4.6'
|
6
|
+
gem 'crack', '0.1.8'
|
7
|
+
gem 'thor', '0.14.6'
|
8
|
+
gem 'hirb'
|
9
|
+
|
10
|
+
group :test do
|
11
|
+
gem 'diff-lcs', '1.1.3'
|
12
|
+
gem 'rspec', '2.6.0'
|
13
|
+
gem 'webmock'
|
14
|
+
end
|
15
|
+
|
16
|
+
group :cucumber do
|
17
|
+
gem 'cucumber', '1.0.6'
|
18
|
+
gem 'aruba', '0.3.6'
|
19
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.2.6)
|
5
|
+
aruba (0.3.6)
|
6
|
+
childprocess (>= 0.1.7)
|
7
|
+
cucumber (>= 0.10.0)
|
8
|
+
rspec (>= 2.5.0)
|
9
|
+
builder (3.0.0)
|
10
|
+
childprocess (0.2.2)
|
11
|
+
ffi (~> 1.0.6)
|
12
|
+
crack (0.1.8)
|
13
|
+
cucumber (1.0.6)
|
14
|
+
builder (>= 2.1.2)
|
15
|
+
diff-lcs (>= 1.1.2)
|
16
|
+
gherkin (~> 2.4.18)
|
17
|
+
json (>= 1.4.6)
|
18
|
+
term-ansicolor (>= 1.0.6)
|
19
|
+
diff-lcs (1.1.3)
|
20
|
+
ffi (1.0.9)
|
21
|
+
gherkin (2.4.21)
|
22
|
+
json (>= 1.4.6)
|
23
|
+
hirb (0.5.0)
|
24
|
+
httparty (0.7.8)
|
25
|
+
crack (= 0.1.8)
|
26
|
+
json (1.4.6)
|
27
|
+
rake (0.8.7)
|
28
|
+
rspec (2.6.0)
|
29
|
+
rspec-core (~> 2.6.0)
|
30
|
+
rspec-expectations (~> 2.6.0)
|
31
|
+
rspec-mocks (~> 2.6.0)
|
32
|
+
rspec-core (2.6.4)
|
33
|
+
rspec-expectations (2.6.0)
|
34
|
+
diff-lcs (~> 1.1.2)
|
35
|
+
rspec-mocks (2.6.0)
|
36
|
+
term-ansicolor (1.0.6)
|
37
|
+
thor (0.14.6)
|
38
|
+
webmock (1.7.6)
|
39
|
+
addressable (~> 2.2, > 2.2.5)
|
40
|
+
crack (>= 0.1.7)
|
41
|
+
|
42
|
+
PLATFORMS
|
43
|
+
ruby
|
44
|
+
|
45
|
+
DEPENDENCIES
|
46
|
+
aruba (= 0.3.6)
|
47
|
+
crack (= 0.1.8)
|
48
|
+
cucumber (= 1.0.6)
|
49
|
+
diff-lcs (= 1.1.3)
|
50
|
+
hirb
|
51
|
+
httparty (= 0.7.8)
|
52
|
+
json (= 1.4.6)
|
53
|
+
rake (= 0.8.7)
|
54
|
+
rspec (= 2.6.0)
|
55
|
+
thor (= 0.14.6)
|
56
|
+
webmock
|
data/LICENSE
ADDED
File without changes
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
Morale CLI
|
2
|
+
==========
|
3
|
+
|
4
|
+
Description
|
5
|
+
-----------
|
6
|
+
|
7
|
+
This gem allows you to create and manage Morale tickets from the command line. It contains a client application that communicates via
|
8
|
+
the Morale API. You must have the API enabled on the account you wish to work with (which is turned off by default) and a valid API key.
|
9
|
+
|
10
|
+
For more information about Morale see <http://teammorale.com>.
|
11
|
+
|
12
|
+
Setup
|
13
|
+
-----
|
14
|
+
|
15
|
+
gem install morale
|
16
|
+
|
17
|
+
The first time you run a command, you will be prompted for your email address, password and a Morale account to work with. You can change the Morale account and project you would like to work with at any time. The account, project, and your API key is stored locally on your machine.
|
18
|
+
|
19
|
+
Available Commands
|
20
|
+
------------------
|
21
|
+
|
22
|
+
The same commands that are available on the web application are available within the CLI gem.
|
23
|
+
|
24
|
+
morale login # Asks for your email address and password for your Morale account and pulls down your API key
|
25
|
+
morale accounts # Displays a list of accounts for your email address and password
|
26
|
+
morale projects # Displays a list of projects for your current account
|
27
|
+
morale [command] # Creates, updates, or deletes a ticket based on your command.
|
28
|
+
|
29
|
+
# Some sample ticket commands
|
30
|
+
|
31
|
+
morale This is a test task assign: Jamie due: today # Task with the title "This is a test task" assigned to Jamie W. with a due date of today
|
32
|
+
morale #35: assign: Robert # Updates ticket #35 by assigning it to Robert
|
33
|
+
morale a #2 # Archives ticket #2
|
34
|
+
morale d #41 # Deletes ticket #41
|
35
|
+
|
36
|
+
|
37
|
+
Released under the [MIT license](http://www.opensource.org/licenses/mit-license.php).
|
38
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
#############################################################################
|
6
|
+
#
|
7
|
+
# Helper functions
|
8
|
+
#
|
9
|
+
#############################################################################
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
13
|
+
end
|
14
|
+
|
15
|
+
def version
|
16
|
+
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
17
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def date
|
21
|
+
Date.today.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def rubyforge_project
|
25
|
+
name
|
26
|
+
end
|
27
|
+
|
28
|
+
def gemspec_file
|
29
|
+
"#{name}.gemspec"
|
30
|
+
end
|
31
|
+
|
32
|
+
def gem_file
|
33
|
+
"#{name}-#{version}.gem"
|
34
|
+
end
|
35
|
+
|
36
|
+
def replace_header(head, header_name)
|
37
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
38
|
+
end
|
39
|
+
|
40
|
+
#############################################################################
|
41
|
+
#
|
42
|
+
# Standard tasks
|
43
|
+
#
|
44
|
+
#############################################################################
|
45
|
+
|
46
|
+
task :default => :test
|
47
|
+
|
48
|
+
require 'rake/testtask'
|
49
|
+
Rake::TestTask.new(:test) do |test|
|
50
|
+
test.libs << 'lib' << 'test'
|
51
|
+
test.pattern = 'test/**/test_*.rb'
|
52
|
+
test.verbose = true
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Generate RCov test coverage and open in your browser"
|
56
|
+
task :coverage do
|
57
|
+
require 'rcov'
|
58
|
+
sh "rm -fr coverage"
|
59
|
+
sh "rcov test/test_*.rb"
|
60
|
+
sh "open coverage/index.html"
|
61
|
+
end
|
62
|
+
|
63
|
+
require 'rake/rdoctask'
|
64
|
+
Rake::RDocTask.new do |rdoc|
|
65
|
+
rdoc.rdoc_dir = 'rdoc'
|
66
|
+
rdoc.title = "#{name} #{version}"
|
67
|
+
rdoc.rdoc_files.include('README*')
|
68
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
69
|
+
end
|
70
|
+
|
71
|
+
desc "Open an irb session preloaded with this library"
|
72
|
+
task :console do
|
73
|
+
sh "irb -rubygems -r ./lib/#{name}.rb"
|
74
|
+
end
|
75
|
+
|
76
|
+
#############################################################################
|
77
|
+
#
|
78
|
+
# Custom tasks (add your own tasks here)
|
79
|
+
#
|
80
|
+
#############################################################################
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
#############################################################################
|
85
|
+
#
|
86
|
+
# Packaging tasks
|
87
|
+
#
|
88
|
+
#############################################################################
|
89
|
+
|
90
|
+
desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
|
91
|
+
task :release => :build do
|
92
|
+
unless `git branch` =~ /^\* master$/
|
93
|
+
puts "You must be on the master branch to release!"
|
94
|
+
exit!
|
95
|
+
end
|
96
|
+
sh "git commit --allow-empty -a -m 'Release #{version}'"
|
97
|
+
sh "git tag v#{version}"
|
98
|
+
sh "git push origin master"
|
99
|
+
sh "git push origin v#{version}"
|
100
|
+
sh "gem push pkg/#{name}-#{version}.gem"
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "Build #{gem_file} into the pkg directory"
|
104
|
+
task :build => :gemspec do
|
105
|
+
sh "mkdir -p pkg"
|
106
|
+
sh "gem build #{gemspec_file}"
|
107
|
+
sh "mv #{gem_file} pkg"
|
108
|
+
end
|
109
|
+
|
110
|
+
desc "Generate #{gemspec_file}"
|
111
|
+
task :gemspec => :validate do
|
112
|
+
# read spec file and split out manifest section
|
113
|
+
spec = File.read(gemspec_file)
|
114
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
115
|
+
|
116
|
+
# replace name version and date
|
117
|
+
replace_header(head, :name)
|
118
|
+
replace_header(head, :version)
|
119
|
+
replace_header(head, :date)
|
120
|
+
#comment this out if your rubyforge_project has a different name
|
121
|
+
replace_header(head, :rubyforge_project)
|
122
|
+
|
123
|
+
# determine file list from git ls-files
|
124
|
+
files = `git ls-files`.
|
125
|
+
split("\n").
|
126
|
+
sort.
|
127
|
+
reject { |file| file =~ /^\./ }.
|
128
|
+
reject { |file| file =~ /^(rdoc|pkg)/ }.
|
129
|
+
map { |file| " #{file}" }.
|
130
|
+
join("\n")
|
131
|
+
|
132
|
+
# piece file back together and write
|
133
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
134
|
+
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
|
135
|
+
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
136
|
+
puts "Updated #{gemspec_file}"
|
137
|
+
end
|
138
|
+
|
139
|
+
desc "Validate #{gemspec_file}"
|
140
|
+
task :validate do
|
141
|
+
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
|
142
|
+
unless libfiles.empty?
|
143
|
+
puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
|
144
|
+
exit!
|
145
|
+
end
|
146
|
+
unless Dir['VERSION*'].empty?
|
147
|
+
puts "A `VERSION` file at root level violates Gem best practices."
|
148
|
+
exit!
|
149
|
+
end
|
150
|
+
end
|
data/bin/morale
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
Feature: Running the accounts command
|
2
|
+
In order to view and select an account to work with
|
3
|
+
As a command line user of Morale
|
4
|
+
I should be able to run accounts to view available accounts and store my selected account information locally
|
5
|
+
|
6
|
+
@interactive
|
7
|
+
Scenario: Running accounts should not require an email address if the api key is stored
|
8
|
+
Given a file named "credentials" with:
|
9
|
+
"""
|
10
|
+
spartan
|
11
|
+
12345
|
12
|
+
"""
|
13
|
+
When I run `morale accounts` interactively
|
14
|
+
Then the output should contain:
|
15
|
+
"""
|
16
|
+
1. Spartan Design
|
17
|
+
"""
|
18
|
+
|
19
|
+
@interactive
|
20
|
+
Scenario: Running accounts with the --change option should ask me to change the account
|
21
|
+
Given a file named "credentials" with:
|
22
|
+
"""
|
23
|
+
spartan
|
24
|
+
12345
|
25
|
+
"""
|
26
|
+
When I run `morale accounts --change` interactively
|
27
|
+
Then the output should contain:
|
28
|
+
"""
|
29
|
+
Choose an account:
|
30
|
+
"""
|
31
|
+
|
32
|
+
@interactive
|
33
|
+
Scenario: Selecting an invalid account when running accounts with the --change option should mention that there is an invalid account
|
34
|
+
Given a file named "credentials" with:
|
35
|
+
"""
|
36
|
+
spartan
|
37
|
+
12345
|
38
|
+
"""
|
39
|
+
When I run `morale accounts --change` interactively
|
40
|
+
And I type "9"
|
41
|
+
Then the output should contain:
|
42
|
+
"""
|
43
|
+
Invalid account.
|
44
|
+
"""
|
45
|
+
|
46
|
+
@interactive
|
47
|
+
Scenario: Running accounts without being authorized should ask for credentials
|
48
|
+
Given a file named "credentials" with:
|
49
|
+
"""
|
50
|
+
"""
|
51
|
+
And I run `morale accounts` interactively
|
52
|
+
Then the output should contain:
|
53
|
+
"""
|
54
|
+
Authentication failure
|
55
|
+
"""
|
56
|
+
|
57
|
+
@interactive
|
58
|
+
Scenario: Running account id should change the account
|
59
|
+
Given a file named "credentials" with:
|
60
|
+
"""
|
61
|
+
spartan
|
62
|
+
12345
|
63
|
+
"""
|
64
|
+
When I run `morale account 2` interactively
|
65
|
+
Then the file "credentials" should contain exactly:
|
66
|
+
"""
|
67
|
+
wolverine
|
68
|
+
12345
|
69
|
+
|
70
|
+
"""
|
71
|
+
|
72
|
+
@interactive
|
73
|
+
Scenario: Running account id with an invalid id should should mention that there is an invalid account
|
74
|
+
Given a file named "credentials" with:
|
75
|
+
"""
|
76
|
+
spartan
|
77
|
+
12345
|
78
|
+
"""
|
79
|
+
When I run `morale account 9` interactively
|
80
|
+
Then the output should contain:
|
81
|
+
"""
|
82
|
+
Invalid account.
|
83
|
+
"""
|
84
|
+
And the file "credentials" should contain exactly:
|
85
|
+
"""
|
86
|
+
spartan
|
87
|
+
12345
|
88
|
+
"""
|
@@ -0,0 +1,65 @@
|
|
1
|
+
Feature: Running the login command
|
2
|
+
In order to set the current user's authentication information
|
3
|
+
As a command line user of Morale
|
4
|
+
I should be able to run login to store my authentication information locally
|
5
|
+
|
6
|
+
# Background:
|
7
|
+
# Given in service reports api_key: "12345" for email: "jimmy@example.com", password: "test", subdomain: "spartan"
|
8
|
+
|
9
|
+
@interactive
|
10
|
+
Scenario: Running login should ask for the account information
|
11
|
+
When I run `morale login` interactively
|
12
|
+
Then the output should contain:
|
13
|
+
"""
|
14
|
+
No account specified for Morale.
|
15
|
+
"""
|
16
|
+
|
17
|
+
@interactive
|
18
|
+
Scenario: Running login should display account information in a list
|
19
|
+
When I run `morale login` interactively
|
20
|
+
And I type "jimmy@example.com"
|
21
|
+
Then the output should contain:
|
22
|
+
"""
|
23
|
+
1. Spartan Design
|
24
|
+
2. Wolverine Pictures
|
25
|
+
Choose an account:
|
26
|
+
"""
|
27
|
+
|
28
|
+
@interactive
|
29
|
+
Scenario: Running login with an invalid email address should display a message that the user is invalid
|
30
|
+
When I run `morale login` interactively
|
31
|
+
And I type "someone@somewhere-else.com"
|
32
|
+
Then the output should contain:
|
33
|
+
"""
|
34
|
+
Email is not registered.
|
35
|
+
"""
|
36
|
+
|
37
|
+
@interactive
|
38
|
+
Scenario: Choosing an invalid account id should display a message that the account is invalid
|
39
|
+
When I run `morale login` interactively
|
40
|
+
And I type "jimmy@example.com"
|
41
|
+
And I type "9"
|
42
|
+
Then the output should contain:
|
43
|
+
"""
|
44
|
+
Invalid account.
|
45
|
+
"""
|
46
|
+
|
47
|
+
@interactive
|
48
|
+
Scenario: Choosing an invalid account id should allow for a retry
|
49
|
+
When I run `morale login` interactively
|
50
|
+
And I type "jimmy@example.com"
|
51
|
+
And I type "9"
|
52
|
+
Then the output should contain:
|
53
|
+
"""
|
54
|
+
1. Spartan Design
|
55
|
+
2. Wolverine Pictures
|
56
|
+
Choose an account:
|
57
|
+
"""
|
58
|
+
|
59
|
+
Scenario: Running login successfully to store my authentication information
|
60
|
+
When I run `morale login` interactively
|
61
|
+
And I type "jimmy@example.com"
|
62
|
+
And I type "1"
|
63
|
+
And I type "test"
|
64
|
+
Then the file "credentials" should contain "spartan"
|
65
|
+
And the file "credentials" should contain "12345"
|
@@ -0,0 +1,110 @@
|
|
1
|
+
Feature: Running the projects command
|
2
|
+
In order to view and select a project to work with
|
3
|
+
As a command line user of Morale
|
4
|
+
I should be able to run projects to view available projects and store my selected project information locally
|
5
|
+
|
6
|
+
@interactive
|
7
|
+
Scenario: Running projects should not require authorization if the account and api key are stored
|
8
|
+
Given a file named "credentials" with:
|
9
|
+
"""
|
10
|
+
spartan
|
11
|
+
12345
|
12
|
+
"""
|
13
|
+
When I run `morale projects` interactively
|
14
|
+
Then the output should contain:
|
15
|
+
"""
|
16
|
+
1. Skunk Works
|
17
|
+
2. Spin Free Project
|
18
|
+
"""
|
19
|
+
|
20
|
+
@interactive
|
21
|
+
Scenario: Running projects with the --change option should ask me to change the project
|
22
|
+
Given a file named "credentials" with:
|
23
|
+
"""
|
24
|
+
spartan
|
25
|
+
12345
|
26
|
+
"""
|
27
|
+
When I run `morale projects --change` interactively
|
28
|
+
Then the output should contain:
|
29
|
+
"""
|
30
|
+
Choose a project:
|
31
|
+
"""
|
32
|
+
|
33
|
+
@interactive
|
34
|
+
Scenario: Running projects with the --change option should save the current project
|
35
|
+
Given a file named "credentials" with:
|
36
|
+
"""
|
37
|
+
spartan
|
38
|
+
12345
|
39
|
+
"""
|
40
|
+
When I run `morale projects --change` interactively
|
41
|
+
And I type "2"
|
42
|
+
Then the file "credentials" should contain exactly:
|
43
|
+
"""
|
44
|
+
spartan
|
45
|
+
12345
|
46
|
+
1
|
47
|
+
|
48
|
+
"""
|
49
|
+
|
50
|
+
@interactive
|
51
|
+
Scenario: Selecting an invalid project when running projects with the --change option should mention that there is an invalid project
|
52
|
+
Given a file named "credentials" with:
|
53
|
+
"""
|
54
|
+
spartan
|
55
|
+
12345
|
56
|
+
"""
|
57
|
+
When I run `morale projects --change` interactively
|
58
|
+
And I type "9"
|
59
|
+
Then the output should contain:
|
60
|
+
"""
|
61
|
+
Invalid project.
|
62
|
+
"""
|
63
|
+
|
64
|
+
@interactive
|
65
|
+
Scenario: Running projects without being authorized should ask for credentials
|
66
|
+
Given a file named "credentials" with:
|
67
|
+
"""
|
68
|
+
"""
|
69
|
+
And I run `morale projects` interactively
|
70
|
+
Then the output should contain:
|
71
|
+
"""
|
72
|
+
Authentication failure
|
73
|
+
"""
|
74
|
+
|
75
|
+
@interactive
|
76
|
+
Scenario: Running project id should change the project
|
77
|
+
Given a file named "credentials" with:
|
78
|
+
"""
|
79
|
+
spartan
|
80
|
+
12345
|
81
|
+
1
|
82
|
+
"""
|
83
|
+
When I run `morale project 1` interactively
|
84
|
+
Then the file "credentials" should contain exactly:
|
85
|
+
"""
|
86
|
+
spartan
|
87
|
+
12345
|
88
|
+
2
|
89
|
+
|
90
|
+
"""
|
91
|
+
|
92
|
+
@interactive
|
93
|
+
Scenario: Running project id with an invalid id should should mention that there is an invalid project
|
94
|
+
Given a file named "credentials" with:
|
95
|
+
"""
|
96
|
+
spartan
|
97
|
+
12345
|
98
|
+
1
|
99
|
+
"""
|
100
|
+
When I run `morale project 9` interactively
|
101
|
+
Then the output should contain:
|
102
|
+
"""
|
103
|
+
Invalid project.
|
104
|
+
"""
|
105
|
+
And the file "credentials" should contain exactly:
|
106
|
+
"""
|
107
|
+
spartan
|
108
|
+
12345
|
109
|
+
1
|
110
|
+
"""
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Feature: Running the tickets command
|
2
|
+
In order to view and create tickets
|
3
|
+
As a command line user of Morale
|
4
|
+
I should be able to run tickets to view, create, update, and delete tickets
|
5
|
+
|
6
|
+
@interactive
|
7
|
+
Scenario: Running projects should not require authorization if the account and api key are stored
|
8
|
+
Given a file named "credentials" with:
|
9
|
+
"""
|
10
|
+
spartan
|
11
|
+
12345
|
12
|
+
1
|
13
|
+
"""
|
14
|
+
When I run `morale ticket "task: This is a new task as: Jimmy"` interactively
|
15
|
+
Then the output should contain:
|
16
|
+
"""
|
17
|
+
Task
|
18
|
+
"""
|
19
|
+
And the output should contain:
|
20
|
+
"""
|
21
|
+
This is a new task
|
22
|
+
"""
|
23
|
+
And the output should contain:
|
24
|
+
"""
|
25
|
+
Jimmy P.
|
26
|
+
"""
|