itunes_store_transporter 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,4 @@
1
+ require "tmpdir"
1
2
  require "fileutils"
2
3
  require "itunes/store/transporter/errors"
3
4
  require "itunes/store/transporter/command"
@@ -24,7 +25,7 @@ module ITunes
24
25
  options[:destination] = Dir.mktmpdir
25
26
  super
26
27
  ensure
27
- FileUtils.rm_rf(options[:destination])
28
+ FileUtils.rm_rf(options[:destination]) if options[:destination]
28
29
  end
29
30
 
30
31
  protected
@@ -33,13 +34,13 @@ module ITunes
33
34
  path = File.join(options[:destination], "#{id}.itmsp", "metadata.xml")
34
35
 
35
36
  if !File.exists?(path)
36
- raise ITunes::Store::Transporter::TransporterError, "No metadata file exists at #{path}"
37
+ raise TransporterError, "No metadata file exists at #{path}"
37
38
  end
38
39
 
39
40
  begin
40
41
  metadata = File.read(path)
41
42
  rescue StandardError => e
42
- raise ITunes::Store::Transporter::TransporterError, "Failed to read metadata file #{path}: #{e}"
43
+ raise TransporterError, "Failed to read metadata file #{path}: #{e}"
43
44
  end
44
45
 
45
46
  metadata
@@ -10,24 +10,38 @@ module ITunes
10
10
  #
11
11
  class Status < Mode
12
12
  def initialize(*config)
13
- super
13
+ super
14
14
  options.on *VENDOR_ID
15
15
  end
16
16
 
17
17
  protected
18
18
  def handle_success(stdout_lines, stderr_lines, options)
19
19
  status = {}
20
- stdout_lines.each do |line|
21
- next unless line =~ /\A\s*\w/
22
- key, value = line.split(/:\s+/, 2).map(&:strip)
23
- key.gsub!(/\s+/, "_")
24
- key.downcase!
25
- status[key.to_sym] = value
20
+ while line = stdout_lines.shift
21
+ next unless line =~ /\S+/
22
+ if line =~ /\A--+/
23
+ entry = {}
24
+ while line = stdout_lines.shift
25
+ break unless line =~ /\A\s*\w/
26
+ key, value = parse_line(line)
27
+ entry[key] = value
28
+ end
29
+ (status[:status] ||= []) << entry
30
+ else
31
+ key, value = parse_line(line)
32
+ status[key] = value
33
+ end
26
34
  end
27
35
  status
28
- end
29
- end
36
+ end
30
37
 
38
+ def parse_line(line)
39
+ key, value = line.split(/:\s+/, 2).map(&:strip)
40
+ key.gsub!(/\s+/, "_")
41
+ key.downcase!
42
+ [key.to_sym, value]
43
+ end
44
+ end
31
45
  end
32
46
  end
33
47
  end
@@ -20,11 +20,17 @@ module ITunes
20
20
  end
21
21
 
22
22
  protected
23
+ def create_transporter_options(optz)
24
+ # Include the option if false
25
+ optz[:verify_assets] = !optz[:verify_assets] if optz.include?(:verify_assets)
26
+ super
27
+ end
28
+
23
29
  # Verify mode returns 0 if there are no packages to verify but will emit an error message about the lack of packages
24
30
  def handle_success(stdout_lines, stderr_lines, options)
25
- parser = Transporter::OutputParser.new(stderr_lines)
31
+ parser = OutputParser.new(stderr_lines)
26
32
  if parser.errors.any?
27
- raise ITunes::Store::Transporter::ExecutionError.new(parser.errors, 0)
33
+ raise ExecutionError.new(parser.errors, 0)
28
34
  else
29
35
  true
30
36
  end
@@ -34,4 +40,3 @@ module ITunes
34
40
  end
35
41
  end
36
42
  end
37
-
@@ -5,78 +5,77 @@ module ITunes
5
5
  class Transporter
6
6
  class OutputParser
7
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
- #
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
12
 
13
- attr :errors
14
- attr :warnings
13
+ attr :errors
14
+ attr :warnings
15
15
 
16
- ERROR_LINE = />\s+ERROR:\s+(.+)/
17
- WARNING_LINE = />\s+WARN:\s+(.+)/
16
+ ERROR_LINE = />\s+ERROR:\s+(.+)/
17
+ WARNING_LINE = />\s+WARN:\s+(.+)/
18
18
 
19
- # Generic messages we want to ignore.
20
- SKIP_ERRORS = [ /\boperation was not successful/i,
19
+ # Generic messages we want to ignore.
20
+ SKIP_ERRORS = [ /\boperation was not successful/i,
21
21
  /\bunable to verify the package/i,
22
22
  /\bwill NOT be verified/,
23
23
  /^an error has occurred/i,
24
24
  /^an error occurred while/i,
25
25
  /^unknown operation/i,
26
- /\bunable to authenticate the package/i ]
26
+ /\bunable to authenticate the package/i ]
27
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
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
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
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
50
 
51
- # Unique messages only. The block form of uniq() not available on Ruby < 1.9.2
51
+ # Unique messages only. The block form of uniq() is not available on Ruby < 1.9.2
52
52
  [errors, warnings].each do |e|
53
- e.replace(e.inject({}) do |uniq, x|
54
- uniq[x.message] = x
55
- uniq
56
- end.values)
53
+ next if e.empty?
54
+ uniq = {}
55
+ e.replace(e.select { |m| uniq.include?(m.message) ? false : uniq[m.message] = true })
57
56
  end
58
- end
57
+ end
59
58
 
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+)\)$/,
59
+ def create_message(line)
60
+ case line
61
+ when /^(?:ERROR|WARNING)\s+ITMS-(\d+):\s+(.+)/
62
+ code = $1
63
+ message = $2
64
+ when /(.+)\s+\((\d+)\)$/,
66
65
  # 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
66
+ /(.+)\s+errorCode\s+=\s+\((\d+)\)$/
67
+ message = $1
68
+ code = $2
69
+ else
70
+ message = line
71
+ code = nil
72
+ end
74
73
 
75
- message.gsub! /^"/, ""
74
+ message.gsub! /^"/, ""
76
75
  message.gsub! /"(?: at .+)?$/, ""
77
76
 
78
- TransporterMessage.new(message, code ? code.to_i : nil)
79
- end
77
+ TransporterMessage.new(message, code ? code.to_i : nil)
78
+ end
80
79
  end
81
80
  end
82
81
  end
@@ -1,4 +1,5 @@
1
1
  require "childprocess"
2
+ require "itunes/store/transporter/errors"
2
3
 
3
4
  module ITunes
4
5
  module Store
@@ -35,6 +36,8 @@ module ITunes
35
36
  end
36
37
 
37
38
  def exec(argv, &block)
39
+ raise ArgumentError, "block required" unless block_given?
40
+
38
41
  begin
39
42
  process = ChildProcess.build(path, *argv)
40
43
 
@@ -54,7 +57,7 @@ module ITunes
54
57
 
55
58
  poll(stdout[0], stderr[0], &block)
56
59
  rescue ChildProcess::Error, SystemCallError => e
57
- raise ITunes::Store::Transporter::TransporterError, e.message
60
+ raise TransporterError, e.message
58
61
  ensure
59
62
  process.wait if process.alive?
60
63
  [ stdout, stderr ].flatten.each { |io| io.close if !io.closed? }
@@ -68,6 +71,7 @@ module ITunes
68
71
  read = [ stdout, stderr ]
69
72
 
70
73
  loop do
74
+ # TODO: Not working on jruby
71
75
  if ready = select(read, nil, nil, 1)
72
76
  ready.each do |set|
73
77
  next unless set.any?
@@ -1,7 +1,7 @@
1
1
  module ITunes
2
2
  module Store
3
3
  class Transporter
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.2"
5
5
  end
6
6
  end
7
7
  end
@@ -3,12 +3,12 @@ require "stringio"
3
3
 
4
4
  shared_examples_for "a transporter option" do |option, expected|
5
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 }
6
+ expect_shell_args(*expected)
7
7
  subject.run(options.merge(option))
8
8
  end
9
9
  end
10
10
 
11
- shared_examples_for "a vendor_id option" do
11
+ shared_examples_for "a vendor_id option" do
12
12
  it_should_behave_like "a transporter option", { :vendor_id => "vID" }, "-vendor_id", "vID"
13
13
  end
14
14
 
@@ -22,25 +22,25 @@ shared_examples_for "a transporter option that expects a directory" do |option,
22
22
  lambda { subject.run(options.merge(option => "__baaaaahd_directory__")) }.should raise_exception(ITunes::Store::Transporter::OptionError, /does not exist/)
23
23
  end
24
24
  end
25
- end
25
+ end
26
26
 
27
27
  shared_examples_for "a boolean transporter option" do |option, expected|
28
- context "when true" do
28
+ context "when true" do
29
29
  it "creates the command line argument" do
30
- ITunes::Store::Transporter::Shell.any_instance.stub(:exec) { |*arg| arg.first.should include(*expected); 0 }
30
+ expect_shell_args(*expected)
31
31
  subject.run(options.merge(option => true))
32
32
  end
33
33
  end
34
34
 
35
- context "when false" do
35
+ context "when false" do
36
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 }
37
+ ITunes::Store::Transporter::Shell.any_instance.should_receive(:exec) { |*arg| arg.first.should_not include(*expected); 0 }
38
38
  subject.run(options.merge(option => false))
39
39
  end
40
40
  end
41
41
 
42
- context "when not boolean" do
43
- it "raises an OptionError" do
42
+ context "when not boolean" do
43
+ it "raises an OptionError" do
44
44
  lambda { subject.run(options.merge(option => "sshaw")) }.should raise_exception(ITunes::Store::Transporter::OptionError, /does not accept/)
45
45
  end
46
46
  end
@@ -76,7 +76,7 @@ shared_examples_for "a subclass of Command::Base" do
76
76
  it "automatically sets NoPause to true" do
77
77
  ENV["PROGRAMFILES"] = "C:\\"
78
78
  shell = ITunes::Store::Transporter::Shell
79
- shell.any_instance.stub(:exec) { |*arg| arg.first.should include("-WONoPause", "true"); 0 }
79
+ expect_shell_args("-WONoPause", "true")
80
80
  shell.stub(:windows? => true)
81
81
  subject.run(options)
82
82
  end
@@ -166,7 +166,7 @@ shared_examples_for "a subclass of Command::Base" do
166
166
  e.errors.should have(2).items
167
167
 
168
168
  # just check one
169
- e.errors[0].should be_a(ITunes::Store::Transporter::TransporterMessage)
169
+ e.errors[0].should be_a(ITunes::Store::Transporter::TransporterMessage)
170
170
  e.errors[0].code.should == 9000
171
171
  e.errors[0].message.should match("Your audio of screwed up!")
172
172
  e.errors[1].code.should == 4009
@@ -297,7 +297,7 @@ describe ITunes::Store::Transporter::Command::Upload do
297
297
  describe ":log_history" do
298
298
  it_should_behave_like "a transporter option that expects a directory", :log_history, "-loghistory"
299
299
  end
300
-
300
+
301
301
  describe ":success" do
302
302
  it_should_behave_like "a transporter option that expects a directory", :success, "-success"
303
303
  end
@@ -321,27 +321,27 @@ describe ITunes::Store::Transporter::Command::Lookup do
321
321
  before(:each) do
322
322
  @tmpdir = Dir.mktmpdir
323
323
  Dir.stub(:mktmpdir => @tmpdir)
324
-
324
+
325
325
  id = options[:vendor_id] || options[:apple_id]
326
326
  @package = File.join(@tmpdir, "#{id}.itmsp")
327
327
  Dir.mkdir(@package)
328
-
328
+
329
329
  @metadata = "<x>Metadata</x>"
330
330
  File.open(File.join(@package, "metadata.xml"), "w") { |io| io.write(@metadata) }
331
-
332
- mock_output
333
331
  end
334
-
332
+
335
333
  after(:each) { FileUtils.rm_rf(@tmpdir) }
336
-
334
+
337
335
  describe "#run" do
338
- context "when successful" do
336
+ before { mock_output }
337
+
338
+ context "when successful" do
339
339
  it "returns the metadata and deletes the temp directory used to output the metadata" do
340
340
  subject.run(options).should == @metadata
341
341
  File.exists?(@tmpdir).should be_false
342
342
  end
343
343
 
344
- context "when the metadata file was not created" do
344
+ context "when the metadata file was not created" do
345
345
  before { FileUtils.rm_rf(@tmpdir) }
346
346
 
347
347
  it "raises a TransporterError" do
@@ -354,14 +354,14 @@ describe ITunes::Store::Transporter::Command::Lookup do
354
354
  # One of these two should be requied, but they should be mutually exclusive
355
355
  describe "options" do
356
356
  describe ":vendor_id" do
357
- let(:options) { create_options({:vendor_id => "vID"}) }
357
+ let(:options) { create_options({:vendor_id => "vID"}) }
358
358
  it_should_behave_like "a vendor_id option"
359
359
  end
360
360
 
361
361
  describe ":apple_id" do
362
- let(:options) { create_options({:apple_id => "aID"}) }
362
+ let(:options) { create_options({:apple_id => "aID"}) }
363
363
  it_should_behave_like "a transporter option", { :apple_id => "aID" }, "-apple_id", "aID"
364
- end
364
+ end
365
365
  end
366
366
  end
367
367
 
@@ -407,24 +407,45 @@ describe ITunes::Store::Transporter::Command::Status do
407
407
 
408
408
  describe "#run" do
409
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
- }
410
+ context "with a single status" do
411
+ it "returns the status information for the package" do
412
+ mock_output(:stdout => "status.vendor_id_123123", :stderr => "stderr.info")
413
+ subject.run(options).should == {
414
+ :vendor_identifier => "123123",
415
+ :apple_identifier => "123123",
416
+ :itunesconnect_status => "Not ready for sale",
417
+ :status => [ { :upload_created => "2000-01-01 00:00:00",
418
+ :upload_state => "Uploaded",
419
+ :upload_state_id => "1",
420
+ :content_state => "Irie",
421
+ :content_state_id => "2" } ]
422
+ }
423
+ end
424
+ end
425
+
426
+ context "with multiple status" do
427
+ it "returns all the status information for the package" do
428
+ mock_output(:stdout => "status.vendor_id_789789", :stderr => "stderr.info")
429
+ subject.run(options).should == {
430
+ :vendor_identifier => "789789",
431
+ :status => [ { :upload_created => "2000-01-01 00:00:00",
432
+ :upload_state => "Uploaded",
433
+ :upload_state_id => "1",
434
+ :content_state => "Irie",
435
+ :content_state_id => "2" },
436
+ { :upload_created => "2000-01-02 00:00:00",
437
+ :upload_state => "Some Failure",
438
+ :upload_state_id => "2",
439
+ :content_state => "Still Irie",
440
+ :content_state_id => "3" } ]
441
+ }
442
+ end
422
443
  end
423
444
  end
424
445
  end
425
446
 
426
- describe "options" do
427
- describe ":vendor_id" do
447
+ describe "options" do
448
+ describe ":vendor_id" do
428
449
  it_should_behave_like "a vendor_id option"
429
450
  end
430
451
  end
@@ -448,9 +469,9 @@ describe ITunes::Store::Transporter::Command::Verify do
448
469
  end
449
470
  end
450
471
 
451
- # If no packages were verfied it exits with 0 but emits an error message
452
472
  context "with errors" do
453
473
  it "raises an ExecutionError" do
474
+ # If no packages were verfied it exits with 0 but emits an error message
454
475
  mock_output(:exit => 0, :stderr => "stderr.errors");
455
476
  lambda { subject.run(options) }.should raise_exception(ITunes::Store::Transporter::ExecutionError)
456
477
  end
@@ -460,7 +481,19 @@ describe ITunes::Store::Transporter::Command::Verify do
460
481
 
461
482
  describe "options" do
462
483
  describe ":verify_assets" do
463
- it_should_behave_like "a boolean transporter option", :verify_assets, "-disableAssetVerification"
484
+ context "when true" do
485
+ it "does not create the command line argument" do
486
+ ITunes::Store::Transporter::Shell.any_instance.should_receive(:exec) { |*arg| arg.first.should_not include("-disableAssetVerification"); 0 }
487
+ subject.run(options.merge(:verify_assets => true))
488
+ end
489
+ end
490
+
491
+ context "when false" do
492
+ it "creates the command line argument" do
493
+ expect_shell_args("-disableAssetVerification")
494
+ subject.run(options.merge(:verify_assets => false))
495
+ end
496
+ end
464
497
  end
465
498
  end
466
499
  end