itunes_store_transporter 0.0.1
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.
- data/README.rdoc +127 -0
- data/bin/itms +243 -0
- data/lib/itunes/store/transporter.rb +236 -0
- data/lib/itunes/store/transporter/command.rb +103 -0
- data/lib/itunes/store/transporter/command/lookup.rb +55 -0
- data/lib/itunes/store/transporter/command/option.rb +21 -0
- data/lib/itunes/store/transporter/command/providers.rb +35 -0
- data/lib/itunes/store/transporter/command/schema.rb +28 -0
- data/lib/itunes/store/transporter/command/status.rb +34 -0
- data/lib/itunes/store/transporter/command/upload.rb +27 -0
- data/lib/itunes/store/transporter/command/verify.rb +37 -0
- data/lib/itunes/store/transporter/command/version.rb +30 -0
- data/lib/itunes/store/transporter/errors.rb +67 -0
- data/lib/itunes/store/transporter/output_parser.rb +83 -0
- data/lib/itunes/store/transporter/shell.rb +94 -0
- data/lib/itunes/store/transporter/version.rb +7 -0
- data/spec/command_spec.rb +511 -0
- data/spec/errors_spec.rb +52 -0
- data/spec/output_parser_spec.rb +71 -0
- data/spec/shell_spec.rb +52 -0
- data/spec/spec_helper.rb +75 -0
- data/spec/transporter_spec.rb +106 -0
- metadata +123 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
require "itunes/store/transporter/errors"
|
2
|
+
|
3
|
+
module ITunes
|
4
|
+
module Store
|
5
|
+
class Transporter
|
6
|
+
class OutputParser
|
7
|
+
|
8
|
+
##
|
9
|
+
# This class extracts error and warning messages output by +iTMSTransporter+. For each message
|
10
|
+
# is creates an instance of ITunes::Store::Transporter::TransporterMessage
|
11
|
+
#
|
12
|
+
|
13
|
+
attr :errors
|
14
|
+
attr :warnings
|
15
|
+
|
16
|
+
ERROR_LINE = />\s+ERROR:\s+(.+)/
|
17
|
+
WARNING_LINE = />\s+WARN:\s+(.+)/
|
18
|
+
|
19
|
+
# Generic messages we want to ignore.
|
20
|
+
SKIP_ERRORS = [ /\boperation was not successful/i,
|
21
|
+
/\bunable to verify the package/i,
|
22
|
+
/\bwill NOT be verified/,
|
23
|
+
/^an error has occurred/i,
|
24
|
+
/^an error occurred while/i,
|
25
|
+
/^unknown operation/i,
|
26
|
+
/\bunable to authenticate the package/i ]
|
27
|
+
|
28
|
+
##
|
29
|
+
# === Arguments
|
30
|
+
#
|
31
|
+
# [output (Array)] +iTMSTransporter+ output
|
32
|
+
#
|
33
|
+
def initialize(output)
|
34
|
+
@errors = []
|
35
|
+
@warnings = []
|
36
|
+
parse_output(output) if Array === output
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def parse_output(output)
|
41
|
+
output.each do |line|
|
42
|
+
if line =~ ERROR_LINE
|
43
|
+
error = $1
|
44
|
+
next if SKIP_ERRORS.any? { |skip| error =~ skip }
|
45
|
+
errors << create_message(error)
|
46
|
+
elsif line =~ WARNING_LINE
|
47
|
+
warnings << create_message($1)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Unique messages only. The block form of uniq() not available on Ruby < 1.9.2
|
52
|
+
[errors, warnings].each do |e|
|
53
|
+
e.replace(e.inject({}) do |uniq, x|
|
54
|
+
uniq[x.message] = x
|
55
|
+
uniq
|
56
|
+
end.values)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def create_message(line)
|
61
|
+
case line
|
62
|
+
when /^(?:ERROR|WARNING)\s+ITMS-(\d+):\s+(.+)/
|
63
|
+
code = $1
|
64
|
+
message = $2
|
65
|
+
when /(.+)\s+\((\d+)\)$/,
|
66
|
+
# Is this correct?
|
67
|
+
/(.+)\s+errorCode\s+=\s+\((\d+)\)$/
|
68
|
+
message = $1
|
69
|
+
code = $2
|
70
|
+
else
|
71
|
+
message = line
|
72
|
+
code = nil
|
73
|
+
end
|
74
|
+
|
75
|
+
message.gsub! /^"/, ""
|
76
|
+
message.gsub! /"(?: at .+)?$/, ""
|
77
|
+
|
78
|
+
TransporterMessage.new(message, code ? code.to_i : nil)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require "childprocess"
|
2
|
+
|
3
|
+
module ITunes
|
4
|
+
module Store
|
5
|
+
class Transporter
|
6
|
+
|
7
|
+
class Shell # :nodoc:
|
8
|
+
attr :path
|
9
|
+
|
10
|
+
EXE_NAME = "iTMSTransporter"
|
11
|
+
WINDOWS_EXE = "#{EXE_NAME}.CMD"
|
12
|
+
DEFAULT_UNIX_PATH = "/usr/local/itms/bin/#{EXE_NAME}"
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def windows?
|
16
|
+
# We just need to know where iTMSTransporter lives, though cygwin
|
17
|
+
# can crow when it receives a Windows path.
|
18
|
+
ChildProcess.windows? || ChildProcess.os == :cygwin
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_path
|
22
|
+
if windows?
|
23
|
+
# The Transporter installer prefers x86
|
24
|
+
# But... I think ruby normalizes this to just PROGRAMFILES
|
25
|
+
root = ENV["PROGRAMFILES(x86)"] || ENV["PROGRAMFILES"] # Need C:\ in case?
|
26
|
+
File.join(root, "itms", WINDOWS_EXE)
|
27
|
+
else
|
28
|
+
DEFAULT_UNIX_PATH
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(path = nil)
|
34
|
+
@path = path || self.class.default_path
|
35
|
+
end
|
36
|
+
|
37
|
+
def exec(argv, &block)
|
38
|
+
begin
|
39
|
+
process = ChildProcess.build(path, *argv)
|
40
|
+
|
41
|
+
stdout = IO.pipe
|
42
|
+
stderr = IO.pipe
|
43
|
+
|
44
|
+
stdout[1].sync = true
|
45
|
+
process.io.stdout = stdout[1]
|
46
|
+
|
47
|
+
stderr[1].sync = true
|
48
|
+
process.io.stderr = stderr[1]
|
49
|
+
|
50
|
+
process.start
|
51
|
+
|
52
|
+
stdout[1].close
|
53
|
+
stderr[1].close
|
54
|
+
|
55
|
+
poll(stdout[0], stderr[0], &block)
|
56
|
+
rescue ChildProcess::Error, SystemCallError => e
|
57
|
+
raise ITunes::Store::Transporter::TransporterError, e.message
|
58
|
+
ensure
|
59
|
+
process.wait if process.alive?
|
60
|
+
[ stdout, stderr ].flatten.each { |io| io.close if !io.closed? }
|
61
|
+
end
|
62
|
+
|
63
|
+
process.exit_code
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def poll(stdout, stderr)
|
68
|
+
read = [ stdout, stderr ]
|
69
|
+
|
70
|
+
loop do
|
71
|
+
if ready = select(read, nil, nil, 1)
|
72
|
+
ready.each do |set|
|
73
|
+
next unless set.any?
|
74
|
+
|
75
|
+
set.each do |io|
|
76
|
+
if io.eof?
|
77
|
+
read.delete(io)
|
78
|
+
next
|
79
|
+
end
|
80
|
+
|
81
|
+
name = io == stdout ? :stdout : :stderr
|
82
|
+
yield(io.gets, name)
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
break unless read.any?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,511 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "stringio"
|
3
|
+
|
4
|
+
shared_examples_for "a transporter option" do |option, expected|
|
5
|
+
it "creates the correct command line argument" do
|
6
|
+
ITunes::Store::Transporter::Shell.any_instance.stub(:exec) { |*arg| arg.first.should include(*expected); 0 }
|
7
|
+
subject.run(options.merge(option))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
shared_examples_for "a vendor_id option" do
|
12
|
+
it_should_behave_like "a transporter option", { :vendor_id => "vID" }, "-vendor_id", "vID"
|
13
|
+
end
|
14
|
+
|
15
|
+
shared_examples_for "a transporter option that expects a directory" do |option, expected|
|
16
|
+
context "when the directory exists" do
|
17
|
+
it_should_behave_like "a transporter option", {option => "."}, expected, "."
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when the directory does not exist" do
|
21
|
+
it "raises an OptionError" do
|
22
|
+
lambda { subject.run(options.merge(option => "__baaaaahd_directory__")) }.should raise_exception(ITunes::Store::Transporter::OptionError, /does not exist/)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
shared_examples_for "a boolean transporter option" do |option, expected|
|
28
|
+
context "when true" do
|
29
|
+
it "creates the command line argument" do
|
30
|
+
ITunes::Store::Transporter::Shell.any_instance.stub(:exec) { |*arg| arg.first.should include(*expected); 0 }
|
31
|
+
subject.run(options.merge(option => true))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when false" do
|
36
|
+
it "does not create the command line argument" do
|
37
|
+
ITunes::Store::Transporter::Shell.any_instance.stub(:exec) { |*arg| arg.first.should_not include(*expected); 0 }
|
38
|
+
subject.run(options.merge(option => false))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "when not boolean" do
|
43
|
+
it "raises an OptionError" do
|
44
|
+
lambda { subject.run(options.merge(option => "sshaw")) }.should raise_exception(ITunes::Store::Transporter::OptionError, /does not accept/)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
shared_examples_for "a required option" do |option|
|
50
|
+
it "must have a value" do
|
51
|
+
["", nil].each do |value|
|
52
|
+
lambda { subject.run(options.merge(option => value)) }.should raise_exception(ITunes::Store::Transporter::OptionError, /#{option}/)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
shared_examples_for "a command that accepts a shortname argument" do
|
58
|
+
context "when the shortname's invalid" do
|
59
|
+
it "raises an OptionError" do
|
60
|
+
lambda { subject.run(options.merge(:shortname => "+")) }.should raise_exception(ITunes::Store::Transporter::OptionError, /shortname/)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "when the shortname's valid" do
|
65
|
+
it "does not raise an exception" do
|
66
|
+
mock_output
|
67
|
+
lambda { subject.run(options.merge(:shortname => "Too $hort")) }.should_not raise_exception
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
shared_examples_for "a subclass of Command::Base" do
|
73
|
+
it { should be_a_kind_of(ITunes::Store::Transporter::Command::Base) }
|
74
|
+
|
75
|
+
context "when on Windows" do
|
76
|
+
it "automatically sets NoPause to true" do
|
77
|
+
ENV["PROGRAMFILES"] = "C:\\"
|
78
|
+
shell = ITunes::Store::Transporter::Shell
|
79
|
+
shell.any_instance.stub(:exec) { |*arg| arg.first.should include("-WONoPause", "true"); 0 }
|
80
|
+
shell.stub(:windows? => true)
|
81
|
+
subject.run(options)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "options" do
|
86
|
+
describe ":print_stderr" do
|
87
|
+
before :each do
|
88
|
+
@realerr = $stderr
|
89
|
+
$stderr = StringIO.new
|
90
|
+
mock_output(:stderr => ["ERR 1"])
|
91
|
+
described_class.new(:print_stderr => print?).run(options)
|
92
|
+
end
|
93
|
+
|
94
|
+
after(:each) { $stderr = @realerr }
|
95
|
+
|
96
|
+
context "when true" do
|
97
|
+
let(:print?) { true }
|
98
|
+
|
99
|
+
it "prints to stderr" do
|
100
|
+
$stderr.string.chomp.should == "ERR 1"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context "when false" do
|
105
|
+
let(:print?) { false }
|
106
|
+
|
107
|
+
it "does not print to stderr" do
|
108
|
+
$stderr.string.should be_empty
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# TODO: Needs some DRYing
|
114
|
+
describe ":print_stdout" do
|
115
|
+
before :each do
|
116
|
+
@realout = $stdout
|
117
|
+
$stdout = StringIO.new
|
118
|
+
mock_output(:stdout => ["OUT 1"])
|
119
|
+
described_class.new(:print_stdout => print?).run(options)
|
120
|
+
end
|
121
|
+
|
122
|
+
after(:each) { $stdout = @realout }
|
123
|
+
|
124
|
+
context "when true" do
|
125
|
+
let(:print?) { true }
|
126
|
+
|
127
|
+
it "prints to stdout" do
|
128
|
+
$stdout.string.chomp.should == "OUT 1"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context "when false" do
|
133
|
+
let(:print?) { false }
|
134
|
+
|
135
|
+
it "does not print to stdout" do
|
136
|
+
$stdout.string.should be_empty
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context "when successful" do
|
143
|
+
it "calls #handle_success" do
|
144
|
+
mock_output(:exit => 0)
|
145
|
+
subject.should_receive(:handle_success)
|
146
|
+
subject.should_not_receive(:handle_error)
|
147
|
+
subject.run(options)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context "when not successful" do
|
152
|
+
it "calls #handler_error" do
|
153
|
+
mock_output(:exit => 1)
|
154
|
+
subject.should_receive(:handle_error)
|
155
|
+
subject.should_not_receive(:handle_success)
|
156
|
+
subject.run(options)
|
157
|
+
end
|
158
|
+
|
159
|
+
context "when an error is output to stderr" do
|
160
|
+
it "raises an ExecutionError" do
|
161
|
+
mock_output(:exit => 1, :stderr => "stderr.errors")
|
162
|
+
lambda { subject.run(options) }.should raise_error { |e|
|
163
|
+
e.should be_a(ITunes::Store::Transporter::ExecutionError)
|
164
|
+
|
165
|
+
e.exitstatus.should == 1
|
166
|
+
e.errors.should have(2).items
|
167
|
+
|
168
|
+
# just check one
|
169
|
+
e.errors[0].should be_a(ITunes::Store::Transporter::TransporterMessage)
|
170
|
+
e.errors[0].code.should == 9000
|
171
|
+
e.errors[0].message.should match("Your audio of screwed up!")
|
172
|
+
e.errors[1].code.should == 4009
|
173
|
+
e.errors[1].message.should match("Chapter timecode is just plain wrong")
|
174
|
+
}
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
shared_examples_for "a transporter mode" do
|
181
|
+
it_should_behave_like "a subclass of Command::Base"
|
182
|
+
it { should be_a_kind_of(ITunes::Store::Transporter::Command::Mode) }
|
183
|
+
|
184
|
+
it "requires a username" do
|
185
|
+
args = options
|
186
|
+
args.delete(:username)
|
187
|
+
lambda { subject.run(args) }.should raise_error(ITunes::Store::Transporter::OptionError, /username/)
|
188
|
+
end
|
189
|
+
|
190
|
+
it "requires a password" do
|
191
|
+
args = options
|
192
|
+
args.delete(:password)
|
193
|
+
lambda { subject.run(args) }.should raise_error(ITunes::Store::Transporter::OptionError, /password/)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
shared_examples_for "a command that requires a package argument" do
|
198
|
+
it_should_behave_like "a required option", :package
|
199
|
+
|
200
|
+
context "when a directory" do
|
201
|
+
before(:all) do
|
202
|
+
@tmpdir = Dir.mktmpdir
|
203
|
+
@pkgdir = File.join(@tmpdir, "package.itmsp")
|
204
|
+
Dir.mkdir(@pkgdir)
|
205
|
+
end
|
206
|
+
|
207
|
+
after(:all) { FileUtils.rm_rf(@tmpdir) }
|
208
|
+
|
209
|
+
it "must end in .itmsp" do
|
210
|
+
options = create_options(:package => @tmpdir)
|
211
|
+
lambda { subject.run(options) }.should raise_error(ITunes::Store::Transporter::OptionError, /must match/i)
|
212
|
+
|
213
|
+
mock_output(:exit => 0)
|
214
|
+
options = create_options(:package => @pkgdir)
|
215
|
+
lambda { subject.run(options) }.should_not raise_error
|
216
|
+
end
|
217
|
+
|
218
|
+
it "must exist" do
|
219
|
+
options = create_options(:package => File.join(@tmpdir, "badpkg.itmsp"))
|
220
|
+
lambda { subject.run(options) }.should raise_error(ITunes::Store::Transporter::OptionError, /does not exist/i)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
context "when a file" do
|
225
|
+
it "raises an OptionError" do
|
226
|
+
path = Tempfile.new("").path
|
227
|
+
options = create_options(:package => path)
|
228
|
+
# TODO: Optout's error message will probably be changed to something more descriptive, change this when that happens
|
229
|
+
lambda { subject.run(options) }.should raise_error(ITunes::Store::Transporter::OptionError, /dir/i)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
describe ITunes::Store::Transporter::Command::Providers do
|
235
|
+
it_behaves_like "a transporter mode"
|
236
|
+
|
237
|
+
subject { described_class.new({}) }
|
238
|
+
let(:options) { create_options }
|
239
|
+
its(:mode) { should == "provider" }
|
240
|
+
|
241
|
+
describe "#run" do
|
242
|
+
it "returns the shortname and longname for each provider" do
|
243
|
+
mock_output(:stdout => "providers.two", :stderr => "stderr.info")
|
244
|
+
subject.run(options).should == [ { :longname => "Some Great User", :shortname => "luser" },
|
245
|
+
{ :longname => "Skye's Taco Eating Service Inc.", :shortname => "conmuchacebolla" } ]
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
describe ITunes::Store::Transporter::Command::Upload do
|
251
|
+
it_behaves_like "a transporter mode"
|
252
|
+
it_behaves_like "a command that requires a package argument"
|
253
|
+
it_behaves_like "a command that accepts a shortname argument"
|
254
|
+
|
255
|
+
subject { described_class.new({}) }
|
256
|
+
let(:options) { create_options(:package => create_package, :transport => "Aspera") }
|
257
|
+
after(:each) { FileUtils.rm_rf(options[:package]) }
|
258
|
+
|
259
|
+
describe "#run" do
|
260
|
+
context "when successful" do
|
261
|
+
it "returns true" do
|
262
|
+
mock_output(:stdout => "stdout.success")
|
263
|
+
subject.run(options).should be_true
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
describe "options" do
|
269
|
+
describe ":rate" do
|
270
|
+
it "must be an integer" do
|
271
|
+
lambda { subject.run(options.merge(:rate => "123")) }.should raise_exception(ITunes::Store::Transporter::OptionError, /rate/)
|
272
|
+
end
|
273
|
+
|
274
|
+
it_should_behave_like "a transporter option", {:rate => 123}, "-k", "123"
|
275
|
+
end
|
276
|
+
|
277
|
+
describe ":transport" do
|
278
|
+
%w|Aspera Signiant DAV|.each do |name|
|
279
|
+
context "with #{name}" do
|
280
|
+
it_should_behave_like "a transporter option", {:transport => name}, "-t", name
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
it "is case sensitive" do
|
285
|
+
lambda { subject.run(options.merge(:transport => "aspera")) }.should raise_exception(ITunes::Store::Transporter::OptionError)
|
286
|
+
end
|
287
|
+
|
288
|
+
it "raises an OptionError if the transport is not supported" do
|
289
|
+
lambda { subject.run(options.merge(:transport => "ftp")) }.should raise_exception(ITunes::Store::Transporter::OptionError)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
describe ":delete" do
|
294
|
+
it_should_behave_like "a boolean transporter option", :delete, "-delete"
|
295
|
+
end
|
296
|
+
|
297
|
+
describe ":log_history" do
|
298
|
+
it_should_behave_like "a transporter option that expects a directory", :log_history, "-loghistory"
|
299
|
+
end
|
300
|
+
|
301
|
+
describe ":success" do
|
302
|
+
it_should_behave_like "a transporter option that expects a directory", :success, "-success"
|
303
|
+
end
|
304
|
+
|
305
|
+
describe ":failure" do
|
306
|
+
it_should_behave_like "a transporter option that expects a directory", :failure, "-failure"
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
describe ITunes::Store::Transporter::Command::Lookup do
|
312
|
+
it_behaves_like "a transporter mode"
|
313
|
+
it_behaves_like "a command that accepts a shortname argument"
|
314
|
+
|
315
|
+
subject { described_class.new({}) }
|
316
|
+
|
317
|
+
let(:options) { create_options(:vendor_id => "X") }
|
318
|
+
its(:mode) { should == "lookupMetadata" }
|
319
|
+
|
320
|
+
# Fake the directory iTMSTransporter creates for the metadata
|
321
|
+
before(:each) do
|
322
|
+
@tmpdir = Dir.mktmpdir
|
323
|
+
Dir.stub(:mktmpdir => @tmpdir)
|
324
|
+
|
325
|
+
id = options[:vendor_id] || options[:apple_id]
|
326
|
+
@package = File.join(@tmpdir, "#{id}.itmsp")
|
327
|
+
Dir.mkdir(@package)
|
328
|
+
|
329
|
+
@metadata = "<x>Metadata</x>"
|
330
|
+
File.open(File.join(@package, "metadata.xml"), "w") { |io| io.write(@metadata) }
|
331
|
+
|
332
|
+
mock_output
|
333
|
+
end
|
334
|
+
|
335
|
+
after(:each) { FileUtils.rm_rf(@tmpdir) }
|
336
|
+
|
337
|
+
describe "#run" do
|
338
|
+
context "when successful" do
|
339
|
+
it "returns the metadata and deletes the temp directory used to output the metadata" do
|
340
|
+
subject.run(options).should == @metadata
|
341
|
+
File.exists?(@tmpdir).should be_false
|
342
|
+
end
|
343
|
+
|
344
|
+
context "when the metadata file was not created" do
|
345
|
+
before { FileUtils.rm_rf(@tmpdir) }
|
346
|
+
|
347
|
+
it "raises a TransporterError" do
|
348
|
+
lambda { subject.run(options) }.should raise_exception(ITunes::Store::Transporter::TransporterError, /no metadata file/i)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# One of these two should be requied, but they should be mutually exclusive
|
355
|
+
describe "options" do
|
356
|
+
describe ":vendor_id" do
|
357
|
+
let(:options) { create_options({:vendor_id => "vID"}) }
|
358
|
+
it_should_behave_like "a vendor_id option"
|
359
|
+
end
|
360
|
+
|
361
|
+
describe ":apple_id" do
|
362
|
+
let(:options) { create_options({:apple_id => "aID"}) }
|
363
|
+
it_should_behave_like "a transporter option", { :apple_id => "aID" }, "-apple_id", "aID"
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
describe ITunes::Store::Transporter::Command::Schema do
|
369
|
+
it_behaves_like "a transporter mode"
|
370
|
+
it_behaves_like "a command that accepts a shortname argument"
|
371
|
+
|
372
|
+
subject { described_class.new({}) }
|
373
|
+
let(:options) { create_options(:type => "strict", :version => "film5") }
|
374
|
+
its(:mode) { should == "generateSchema" }
|
375
|
+
|
376
|
+
describe "#run" do
|
377
|
+
context "when successful" do
|
378
|
+
it "returns the requested schema" do
|
379
|
+
mock_output(:stdout => [ "<x>Film Schema</x>" ], :stderr => "stderr.info")
|
380
|
+
subject.run(options).should == "<x>Film Schema</x>"
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
describe "options" do
|
386
|
+
describe ":version" do
|
387
|
+
it_should_behave_like "a transporter option", {:version => "versionX"}, "-schema", "versionX"
|
388
|
+
end
|
389
|
+
|
390
|
+
# Like Upload's :trasport, case sen., limited to a set
|
391
|
+
describe ":type" do
|
392
|
+
%w|transitional strict|.each do |type|
|
393
|
+
context "with #{type}" do
|
394
|
+
it_should_behave_like "a transporter option", {:type => type}, "-schemaType", type
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
describe ITunes::Store::Transporter::Command::Status do
|
402
|
+
it_behaves_like "a transporter mode"
|
403
|
+
|
404
|
+
subject { described_class.new({}) }
|
405
|
+
let(:options) { create_options(:vendor_id => 123123) }
|
406
|
+
its(:mode) { should == "status" }
|
407
|
+
|
408
|
+
describe "#run" do
|
409
|
+
context "when successful" do
|
410
|
+
it "returns the status information for the package" do
|
411
|
+
mock_output(:stdout => "status.vendor_id_123123", :stderr => "stderr.info")
|
412
|
+
subject.run(options).should == {
|
413
|
+
:vendor_identifier => "123123",
|
414
|
+
:apple_identifier => "123123",
|
415
|
+
:itunesconnect_status => "Not ready for sale",
|
416
|
+
:upload_created => "2000-01-01 00:00:00",
|
417
|
+
:upload_state => "Uploaded",
|
418
|
+
:upload_state_id => "1",
|
419
|
+
:content_state => "Irie",
|
420
|
+
:content_state_id => "2"
|
421
|
+
}
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
describe "options" do
|
427
|
+
describe ":vendor_id" do
|
428
|
+
it_should_behave_like "a vendor_id option"
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
describe ITunes::Store::Transporter::Command::Verify do
|
434
|
+
it_behaves_like "a transporter mode"
|
435
|
+
it_behaves_like "a command that requires a package argument"
|
436
|
+
it_behaves_like "a command that accepts a shortname argument"
|
437
|
+
|
438
|
+
subject { described_class.new({}) }
|
439
|
+
its(:mode) { should == "verify" }
|
440
|
+
let(:options) { create_options(:package => create_package) }
|
441
|
+
|
442
|
+
describe "#run" do
|
443
|
+
context "when successful" do #successful means exit(0)
|
444
|
+
context "without any errors" do
|
445
|
+
it "returns true" do
|
446
|
+
mock_output(:stdout => "stdout.success", :stderr => "stderr.info")
|
447
|
+
subject.run(options).should be_true
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
# If no packages were verfied it exits with 0 but emits an error message
|
452
|
+
context "with errors" do
|
453
|
+
it "raises an ExecutionError" do
|
454
|
+
mock_output(:exit => 0, :stderr => "stderr.errors");
|
455
|
+
lambda { subject.run(options) }.should raise_exception(ITunes::Store::Transporter::ExecutionError)
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
describe "options" do
|
462
|
+
describe ":verify_assets" do
|
463
|
+
it_should_behave_like "a boolean transporter option", :verify_assets, "-disableAssetVerification"
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
describe ITunes::Store::Transporter::Command::Version do
|
469
|
+
subject { described_class.new({}) }
|
470
|
+
|
471
|
+
def output_version(v)
|
472
|
+
["iTMSTransporter version #{v}\n"]
|
473
|
+
end
|
474
|
+
|
475
|
+
describe "#run" do
|
476
|
+
context "when the version is major" do
|
477
|
+
it "returns the version" do
|
478
|
+
mock_output(:stdout => output_version("1"))
|
479
|
+
subject.run.should == "1"
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
context "when the version is major.minor" do
|
484
|
+
it "returns the version" do
|
485
|
+
mock_output(:stdout => output_version("1.10"))
|
486
|
+
subject.run.should == "1.10"
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
context "when the version is major.minor.release" do
|
491
|
+
it "returns the version" do
|
492
|
+
mock_output(:stdout => output_version("1.10.100"))
|
493
|
+
subject.run.should == "1.10.100"
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
context "when the version is major.minor.release.build format" do
|
498
|
+
it "returns the version" do
|
499
|
+
mock_output(:stdout => output_version("1.10.100.1234"))
|
500
|
+
subject.run.should == "1.10.100.1234"
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
context "when the version it's somthing else" do
|
505
|
+
it "returns 'Unknown'" do
|
506
|
+
mock_output(:stdout => ["bad version here"])
|
507
|
+
subject.run.should == "Unknown"
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
511
|
+
end
|