ncs_mdes_warehouse 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -41,7 +41,10 @@ module NcsNavigator::Warehouse
41
41
 
42
42
  def unsuccessful_record(record, message)
43
43
  self.transform_errors <<
44
- TransformError.new(:model_class => record.class.name, :message => message)
44
+ TransformError.new(
45
+ :model_class => record.class.name,
46
+ :record_id => (record.key.first if record && record.key),
47
+ :message => message)
45
48
  end
46
49
  end
47
50
 
@@ -54,6 +57,7 @@ module NcsNavigator::Warehouse
54
57
  property :id, Serial
55
58
  property :message, Text, :required => true
56
59
  property :model_class, String, :length => 255
60
+ property :record_id, String, :length => 255
57
61
 
58
62
  belongs_to :transform_status, TransformStatus, :required => true
59
63
  end
@@ -37,16 +37,17 @@ module NcsNavigator::Warehouse::Transformers
37
37
  # @return [void]
38
38
  def transform(status)
39
39
  enum.each do |record|
40
+ apply_global_values_if_necessary(record)
40
41
  if record.valid?
41
42
  log.debug("Saving valid record #{record_ident record}.")
42
43
  begin
43
44
  unless record.save
44
- msg = "Could not save. #{record_ident(record)}."
45
+ msg = "Could not save."
45
46
  log.error msg
46
47
  status.unsuccessful_record(record, msg)
47
48
  end
48
49
  rescue => e
49
- msg = "Error on save. #{e.class}: #{e}. #{record_ident(record)}."
50
+ msg = "Error on save. #{e.class}: #{e}."
50
51
  log.error msg
51
52
  status.unsuccessful_record(record, msg)
52
53
  end
@@ -57,7 +58,7 @@ module NcsNavigator::Warehouse::Transformers
57
58
  "#{e} (#{prop}=#{v.inspect})."
58
59
  }
59
60
  }.flatten
60
- msg = "Invalid record. #{messages.join(' ')} #{record_ident(record)}."
61
+ msg = "Invalid record. #{messages.join(' ')}"
61
62
  log.error msg
62
63
  status.unsuccessful_record(record, msg)
63
64
  end
@@ -72,5 +73,19 @@ module NcsNavigator::Warehouse::Transformers
72
73
  '%s %s=%s' % [
73
74
  rec.class.name.demodulize, rec.class.key.first.name, rec.key.try(:first).inspect]
74
75
  end
76
+
77
+ def apply_global_values_if_necessary(record)
78
+ {
79
+ :psu_id => @configuration.navigator.psus.first.id,
80
+ :recruit_type => @configuration.navigator.recruitment_type_id
81
+ }.each do |attr, value|
82
+ setter = :"#{attr}="
83
+ if record.respond_to?(setter) && record.respond_to?(attr)
84
+ unless record.send(attr)
85
+ record.send(setter, value)
86
+ end
87
+ end
88
+ end
89
+ end
75
90
  end
76
91
  end
@@ -22,6 +22,7 @@ module NcsNavigator::Warehouse
22
22
  say clear_line_chars
23
23
  say s
24
24
  end
25
+ alias :clear_line_and_say :clear_line_then_say
25
26
 
26
27
  def say_line(s)
27
28
  say s, "\n"
@@ -1,5 +1,5 @@
1
1
  module NcsNavigator
2
2
  module Warehouse
3
- VERSION = '0.1.1'
3
+ VERSION = '0.2.0'
4
4
  end
5
5
  end
@@ -7,12 +7,28 @@ require 'fileutils'
7
7
  require 'forwardable'
8
8
 
9
9
  module NcsNavigator::Warehouse
10
+ ##
11
+ # Generates VDR XML from a warehouse instance. This is the object
12
+ # which implements the `emit-xml` tool in the `mdes-wh` command line
13
+ # client.
10
14
  class XmlEmitter
11
15
  extend Forwardable
12
16
 
17
+ ##
18
+ # @return [Configuration] the warehouse configuration used by this
19
+ # emitter.
13
20
  attr_reader :configuration
21
+
22
+ ##
23
+ # @return [Pathname] the file to which the XML will be emitted.
14
24
  attr_reader :filename
15
25
 
26
+ ##
27
+ # @return [Array<Models::MdesModel>] the models whose data will be
28
+ # emitted. This is determined from the `:tables` option to
29
+ # {#initialize}.
30
+ attr_reader :models
31
+
16
32
  def_delegators :@configuration, :shell, :log
17
33
 
18
34
  HEADER_TEMPLATE = ERB.new(<<-XML_ERB)
@@ -39,6 +55,10 @@ XML_ERB
39
55
  </ncs:recruitment_substudy_transmission_envelope>
40
56
  XML
41
57
 
58
+ ##
59
+ # @param configuration [Configuration]
60
+ # @return [Pathname] the default filename to use for a VDR XML
61
+ # submission. The format is `{county}-{YYYYMMDD}.xml`.
42
62
  def self.default_filename(configuration)
43
63
  psu_type = configuration.mdes.types.detect { |type| type.name =~ /^psu_cl/ }
44
64
  unless psu_type
@@ -48,7 +68,7 @@ XML
48
68
  psu_id = configuration.navigator.psus.first.id
49
69
  psu_entry = psu_type.code_list.detect { |cle| cle.value == psu_id }
50
70
  unless psu_entry
51
- fail "Cannot find PSU #{psu_id} in #{psu_type.name}. Please specify a filename manually"
71
+ fail "Cannot find PSU #{psu_id} in #{psu_type.name}. Please specify a filename manually."
52
72
  end
53
73
 
54
74
  Pathname.new '%s-%s.xml' % [
@@ -57,7 +77,26 @@ XML
57
77
  ]
58
78
  end
59
79
 
60
- def initialize(config, filename)
80
+ ##
81
+ # Create a new {XmlEmitter}.
82
+ #
83
+ # @param [Configuration] config the configuration for the
84
+ # warehouse from which to emit records.
85
+ # @param [Pathname,#to_s,nil] filename the filename to which the output
86
+ # will be written. If `nil`, the {.default_filename} is used.
87
+ #
88
+ # @option options [Fixnum] :block-size (5000) the maximum number
89
+ # of records to load into memory before writing them to the XML
90
+ # file. Reduce this to reduce the memory load of the emitter.
91
+ # Increasing it will probably not improve performance, even if
92
+ # you have sufficient memory to load more records.
93
+ # @option options [Boolean] :include-pii (false) should PII
94
+ # variable values be included in the XML?
95
+ # @option options [Array<#to_s>] :tables (all for current MDES
96
+ # version) the tables to include in the emitted XML.
97
+ # @option options [Boolean] :zip (true) should a ZIP file be
98
+ # produced alongside the XML file?
99
+ def initialize(config, filename, options={})
61
100
  @configuration = config
62
101
  @filename = case filename
63
102
  when Pathname
@@ -68,8 +107,23 @@ XML
68
107
  Pathname.new(filename.to_s)
69
108
  end
70
109
  @record_count = 0
110
+ @block_size = options[:'block-size'] || 5000
111
+ @zip = options.has_key?(:zip) ? options[:zip] : true
112
+ @include_pii = options[:'include-pii']
113
+ @models =
114
+ if options[:tables]
115
+ options[:tables].collect { |t| t.to_s }.collect { |t|
116
+ config.models_module.mdes_order.find { |model| model.mdes_table_name == t }
117
+ }
118
+ else
119
+ config.models_module.mdes_order
120
+ end
71
121
  end
72
122
 
123
+ ##
124
+ # Emit XML from the configured warehouse to {#filename}.
125
+ #
126
+ # @return [void]
73
127
  def emit_xml
74
128
  shell.say_line("Exporting to #{filename}")
75
129
  log.info("Beginning XML export to #{filename}")
@@ -78,7 +132,7 @@ XML
78
132
  filename.open('w') do |f|
79
133
  f.write HEADER_TEMPLATE.result(binding)
80
134
 
81
- configuration.models_module.mdes_order.each do |model|
135
+ models.each do |model|
82
136
  shell.clear_line_then_say('Writing XML for %33s' % model.mdes_table_name)
83
137
 
84
138
  write_all_xml_for_model(f, model)
@@ -91,14 +145,36 @@ XML
91
145
  shell.clear_line_then_say(msg)
92
146
  log.info(msg)
93
147
 
94
- shell.say_line("Zipping to #{zip_filename}")
95
- log.info("Zipping to #{zip_filename}")
96
- Zip::ZipFile.open(zip_filename, Zip::ZipFile::CREATE) do |zf|
97
- zf.add(filename.basename, filename)
148
+ if zip?
149
+ shell.say_line("Zipping to #{zip_filename}")
150
+ log.info("Zipping to #{zip_filename}")
151
+ Zip::ZipFile.open(zip_filename, Zip::ZipFile::CREATE) do |zf|
152
+ zf.add(filename.basename, filename)
153
+ end
154
+ log.info("XML export complete")
98
155
  end
99
- log.info("XML export complete")
100
156
  end
101
157
 
158
+ ##
159
+ # Will PII be included in the exported XML?
160
+ #
161
+ # @return [Boolean]
162
+ def include_pii?
163
+ @include_pii
164
+ end
165
+
166
+ ##
167
+ # Will a ZIP archive be created along with the XML?
168
+ #
169
+ # @return [Boolean]
170
+ def zip?
171
+ @zip
172
+ end
173
+
174
+ ##
175
+ # @return [Pathname] the filename for the ZIP archive of the XML,
176
+ # if any. Currently this is always {#filename} + '.zip'.
177
+ # @see #zip?
102
178
  def zip_filename
103
179
  @zip_filename ||= filename.to_s + '.zip'
104
180
  end
@@ -107,11 +183,17 @@ XML
107
183
 
108
184
  def write_all_xml_for_model(f, model)
109
185
  shell.say(' %20s' % '[loading]')
110
- model.all.each do |instance|
111
- instance.write_mdes_xml(f, :indent => 3, :margin => 1)
112
- @record_count += 1
113
-
114
- shell.back_up_and_say(20, '%5d (%5.1f/sec)' % [@record_count, emit_rate])
186
+ count = model.count
187
+ offset = 0
188
+ while offset < count
189
+ shell.back_up_and_say(20, '%20s' % '[loading]')
190
+ model.all(:limit => @block_size, :offset => offset).each do |instance|
191
+ instance.write_mdes_xml(f, :indent => 3, :margin => 1, :pii => include_pii?)
192
+ @record_count += 1
193
+
194
+ shell.back_up_and_say(20, '%5d (%5.1f/sec)' % [@record_count, emit_rate])
195
+ end
196
+ offset += @block_size
115
197
  end
116
198
  end
117
199
 
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
15
15
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
16
  s.require_paths = ["lib", "generated_models"]
17
17
 
18
- s.add_dependency 'ncs_mdes', '~> 0.4', '>= 0.4.2'
18
+ s.add_dependency 'ncs_mdes', '~> 0.5'
19
19
  s.add_dependency 'ncs_navigator_configuration', '~> 0.2'
20
20
 
21
21
  s.add_dependency 'activesupport', '~> 3.0'
@@ -0,0 +1,39 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ module NcsNavigator::Warehouse::DataMapper
4
+ shared_examples 'a string-based NCS type' do
5
+ it 'converts a BigDecimal to a simple number string (no powers of ten)' do
6
+ subject.send(:typecast_to_primitive, BigDecimal.new('0.453E1')).should == '4.53'
7
+ end
8
+
9
+ it 'uses the default method for other types' do
10
+ subject.send(:typecast_to_primitive, 45).should == '45'
11
+ end
12
+ end
13
+
14
+ describe NcsString, :modifies_warehouse_state do
15
+ let(:model) {
16
+ class NcsStringHost
17
+ include DataMapper::Resource
18
+ property :haiku, NcsString
19
+ end
20
+ }
21
+
22
+ subject { model.properties[:haiku] }
23
+
24
+ it_behaves_like 'a string-based NCS type'
25
+ end
26
+
27
+ describe NcsText, :modifies_warehouse_state do
28
+ let(:model) {
29
+ class NcsTextHost
30
+ include DataMapper::Resource
31
+ property :novel, NcsText
32
+ end
33
+ }
34
+
35
+ subject { model.properties[:novel] }
36
+
37
+ it_behaves_like 'a string-based NCS type'
38
+ end
39
+ end
@@ -13,7 +13,9 @@ module NcsNavigator::Warehouse
13
13
  end
14
14
  end
15
15
 
16
- before(:all) do
16
+ before do
17
+ use_test_bcdatabase
18
+
17
19
  # Not config due to RSpec #500
18
20
  DatabaseInitializer.new(base_config).tap do |dbi|
19
21
  dbi.set_up_repository(:both)
@@ -48,7 +48,7 @@ module NcsNavigator::Warehouse::Transformers
48
48
  end
49
49
 
50
50
  describe '#connection_parameters' do
51
- describe 'from bcdatabase', :modifies_warehouse_state do
51
+ describe 'from bcdatabase', :modifies_warehouse_state, :use_test_bcdatabase do
52
52
  let(:cls) {
53
53
  sample_class do
54
54
  bcdatabase :name => 'ncs_staff_portal'
@@ -79,7 +79,7 @@ module NcsNavigator::Warehouse::Transformers
79
79
  end
80
80
  end
81
81
 
82
- describe '#repository' do
82
+ describe '#repository', :modifies_warehouse_state, :use_test_bcdatabase do
83
83
  include_context 'people_pro'
84
84
 
85
85
  let(:cls) do
@@ -97,7 +97,7 @@ module NcsNavigator::Warehouse::Transformers
97
97
  end
98
98
  end
99
99
 
100
- describe '#each' do
100
+ describe '#each', :modifies_warehouse_state, :use_test_bcdatabase do
101
101
  include_context 'people_pro'
102
102
 
103
103
  describe 'of .produce_records' do
@@ -5,6 +5,8 @@ module NcsNavigator::Warehouse::Transformers
5
5
  class Sample
6
6
  include ::DataMapper::Resource
7
7
 
8
+ property :psu_id, String
9
+ property :recruit_type, String
8
10
  property :id, Integer, :key => true
9
11
  property :name, String, :required => true
10
12
  end
@@ -14,7 +16,11 @@ module NcsNavigator::Warehouse::Transformers
14
16
  end
15
17
 
16
18
  describe '#transform' do
17
- let(:config) { NcsNavigator::Warehouse::Configuration.new }
19
+ let(:config) {
20
+ NcsNavigator::Warehouse::Configuration.new.tap do |c|
21
+ c.log_file = tmpdir + 'enum_transformer_test.log'
22
+ end
23
+ }
18
24
  let(:records) { [
19
25
  Sample.new(:id => 1, :name => 'One'),
20
26
  Sample.new(:id => 2, :name => 'Two'),
@@ -33,7 +39,29 @@ module NcsNavigator::Warehouse::Transformers
33
39
  transform_status.transform_errors.should be_empty
34
40
  end
35
41
 
36
- it 'automatically sets the PSU ID if the object accepts it'
42
+ it 'automatically sets the PSU ID if necessary' do
43
+ records[1].psu_id = '20000042'
44
+
45
+ records.each do |m|
46
+ m.should_receive(:valid?).and_return(true)
47
+ m.should_receive(:save).and_return(true)
48
+ end
49
+
50
+ subject.transform(transform_status)
51
+ records.collect(&:psu_id).should == %w(20000030 20000042 20000030)
52
+ end
53
+
54
+ it 'automatically sets recruit_type if necessary' do
55
+ records[2].recruit_type = '1'
56
+
57
+ records.each do |m|
58
+ m.should_receive(:valid?).and_return(true)
59
+ m.should_receive(:save).and_return(true)
60
+ end
61
+
62
+ subject.transform(transform_status)
63
+ records.collect(&:recruit_type).should == %w(3 3 1)
64
+ end
37
65
 
38
66
  describe 'with an invalid instance' do
39
67
  before do
@@ -47,7 +75,8 @@ module NcsNavigator::Warehouse::Transformers
47
75
  it 'records the invalid instance' do
48
76
  err = transform_status.transform_errors.first
49
77
  err.model_class.should == Sample.to_s
50
- err.message.should == 'Invalid record. Name must not be blank (name=nil). Sample id=3.'
78
+ err.record_id.should == '3'
79
+ err.message.should == 'Invalid record. Name must not be blank (name=nil).'
51
80
  end
52
81
 
53
82
  it 'saves the other instances' do
@@ -67,7 +96,8 @@ module NcsNavigator::Warehouse::Transformers
67
96
  it 'records the unsaveable instance' do
68
97
  err = transform_status.transform_errors.first
69
98
  err.model_class.should == Sample.to_s
70
- err.message.should == 'Could not save. Sample id=2.'
99
+ err.record_id.should == '2'
100
+ err.message.should == 'Could not save.'
71
101
  end
72
102
 
73
103
  it 'saves the saveable instances' do
@@ -87,8 +117,9 @@ module NcsNavigator::Warehouse::Transformers
87
117
  it 'records the failing instance' do
88
118
  err = transform_status.transform_errors.first
89
119
  err.model_class.should == Sample.to_s
120
+ err.record_id.should == '1'
90
121
  err.message.should ==
91
- 'Error on save. RuntimeError: No database around these parts. Sample id=1.'
122
+ 'Error on save. RuntimeError: No database around these parts.'
92
123
  end
93
124
 
94
125
  it 'saves the saveable instances' do
@@ -5,8 +5,9 @@ require 'zip/zip'
5
5
  module NcsNavigator::Warehouse
6
6
  describe XmlEmitter, :use_mdes do
7
7
  let(:filename) { tmpdir + 'export.xml' }
8
+ let(:options) { {} }
8
9
  let(:xml) {
9
- XmlEmitter.new(spec_config, filename).emit_xml
10
+ XmlEmitter.new(spec_config, filename, options).emit_xml
10
11
  Nokogiri::XML(File.read(filename))
11
12
  }
12
13
 
@@ -14,33 +15,91 @@ module NcsNavigator::Warehouse
14
15
  spec_config.models_module.const_get(:Person)
15
16
  end
16
17
 
18
+ def participant_model
19
+ spec_config.models_module.const_get(:Participant)
20
+ end
21
+
22
+ def stub_model(model)
23
+ model.stub!(:count).and_return(0)
24
+ end
25
+
17
26
  before do
18
- spec_config.models_module.mdes_order.each do |model|
19
- model.stub!(:all).and_return([])
27
+ spec_config.models_module.mdes_order.reject { |m|
28
+ [person_model, participant_model].include?(m)
29
+ }.each do |model|
30
+ stub_model(model)
20
31
  end
21
32
  end
22
33
 
23
34
  # Most of the details of the XML are tested on the MdesModel mixin
24
35
  describe 'the generated XML', :slow do
25
- it 'includes the SC from the configuration' do
26
- xml.xpath('//sc_id').text.should == '20000029'
36
+ describe 'global attributes' do
37
+ before do
38
+ stub_model(person_model)
39
+ stub_model(participant_model)
40
+ end
41
+
42
+ it 'includes the SC from the configuration' do
43
+ xml.xpath('//sc_id').text.should == '20000029'
44
+ end
45
+
46
+ it 'includes the PSU from the configuration' do
47
+ xml.xpath('//psu_id').text.should == '20000030'
48
+ end
49
+
50
+ it 'includes the appropriate specification version' do
51
+ xml.xpath('//specification_version').text.
52
+ should == spec_config.mdes.specification_version
53
+ end
54
+ end
55
+
56
+ def default_required_attributes(model)
57
+ model.properties.select { |prop| prop.required? }.inject({}) { |h, prop|
58
+ h[prop.name] = '-4'; h
59
+ }
60
+ end
61
+
62
+ def create_instance(model, attributes)
63
+ model.new(default_required_attributes(model).merge(attributes))
27
64
  end
28
65
 
29
- it 'includes the PSU from the configuration' do
30
- xml.xpath('//psu_id').text.should == '20000030'
66
+ def create_person(id, attributes={})
67
+ create_instance(person_model, { :person_id => id }.merge(attributes))
31
68
  end
32
69
 
33
- it 'includes the appropriate specification version' do
34
- xml.xpath('//specification_version').text.
35
- should == spec_config.mdes.specification_version
70
+ describe 'with exactly one actual record', :slow, :use_database do
71
+ let(:records) {
72
+ [
73
+ create_person('XQ4')
74
+ ]
75
+ }
76
+
77
+ before do
78
+ pending 'Not working in CI at the moment' if ENV['CI_RUBY']
79
+ records.each { |rec| rec.save or fail "Save of #{rec.inspect} failed." }
80
+ end
81
+
82
+ it 'contains records for all models' do
83
+ xml.xpath('//person').size.should == 1
84
+ end
85
+
86
+ it 'contains the right records' do
87
+ xml.xpath('//person/person_id').collect { |e| e.text.strip }.sort.should == %w(XQ4)
88
+ end
36
89
  end
37
90
 
38
- describe 'with actual data' do
91
+ describe 'with actual data', :slow, :use_database do
92
+ let(:records) {
93
+ [
94
+ create_person('XQ4', :first_name => 'Xavier'),
95
+ create_person('QX9', :first_name => 'Quentin'),
96
+ create_instance(participant_model, :p_id => 'P_QX4')
97
+ ]
98
+ }
99
+
39
100
  before do
40
- person_model.should_receive(:all).and_return([
41
- person_model.new(:person_id => 'XQ4'),
42
- person_model.new(:person_id => 'QX9')
43
- ])
101
+ pending 'Not working in CI at the moment' if ENV['CI_RUBY']
102
+ records.each { |rec| rec.save or fail "Save of #{rec.inspect} failed." }
44
103
  end
45
104
 
46
105
  it 'contains records for all models' do
@@ -48,7 +107,70 @@ module NcsNavigator::Warehouse
48
107
  end
49
108
 
50
109
  it 'contains the right records' do
51
- xml.xpath('//person/person_id').collect { |e| e.text.strip }.should == %w(XQ4 QX9)
110
+ xml.xpath('//person/person_id').collect { |e| e.text.strip }.sort.should == %w(QX9 XQ4)
111
+ end
112
+
113
+ describe 'and PII' do
114
+ let(:xml_first_names) {
115
+ xml.xpath('//person/first_name').collect { |e| e.text.strip }.sort
116
+ }
117
+
118
+ it 'excludes PII by default' do
119
+ xml_first_names.should == ['', '']
120
+ end
121
+
122
+ it 'excludes PII when explicitly excluded' do
123
+ options[:'include-pii'] = false
124
+ xml_first_names.should == ['', '']
125
+ end
126
+
127
+ it 'includes PII when requested' do
128
+ options[:'include-pii'] = true
129
+ xml_first_names.should == %w(Quentin Xavier)
130
+ end
131
+ end
132
+
133
+ describe 'and selected output' do
134
+ let(:people_count) { xml.xpath('//person').size }
135
+ let(:p_count) { xml.xpath('//participant').size }
136
+
137
+ it 'includes all tables by default' do
138
+ people_count.should == 2
139
+ p_count.should == 1
140
+ end
141
+
142
+ it 'includes only the selected tables when requested' do
143
+ options[:tables] = %w(participant)
144
+
145
+ people_count.should == 0
146
+ p_count.should == 1
147
+ end
148
+
149
+ it 'includes all the requested tables when explicitly requested' do
150
+ options[:tables] = spec_config.models_module.mdes_order.collect(&:mdes_table_name)
151
+
152
+ people_count.should == 2
153
+ p_count.should == 1
154
+ end
155
+ end
156
+ end
157
+
158
+ describe 'with lots and lots of actual data', :slow, :use_database do
159
+ let(:count) { 3134 }
160
+ let(:records) { (0...count).collect { |n| create_person(n) } }
161
+ let(:actual_ids) { xml.xpath('//person/person_id').collect { |e| e.text.strip } }
162
+
163
+ before do
164
+ pending 'Not working in CI at the moment' if ENV['CI_RUBY']
165
+ records.each { |rec| rec.save or fail "Save of #{rec.inspect} failed." }
166
+ end
167
+
168
+ it 'contains all the records' do
169
+ actual_ids.size.should == count
170
+ end
171
+
172
+ it 'contains the right records' do
173
+ actual_ids.collect(&:to_i).sort[2456].should == 2456
52
174
  end
53
175
  end
54
176
  end
@@ -57,27 +179,51 @@ module NcsNavigator::Warehouse
57
179
  let(:expected_zipfile) { Pathname.new(filename.to_s + '.zip') }
58
180
 
59
181
  before do
60
- xml # for side effects
182
+ stub_model(person_model)
183
+ stub_model(participant_model)
61
184
  end
62
185
 
63
- it 'exists' do
64
- expected_zipfile.should be_readable
65
- end
186
+ context do
187
+ def actual
188
+ xml
189
+ expected_zipfile
190
+ end
191
+
192
+ it 'exists by default' do
193
+ actual.should be_readable
194
+ end
66
195
 
67
- it 'contains just the XML file' do
68
- contents = []
69
- Zip::ZipFile.foreach(expected_zipfile) do |entry|
70
- contents << entry.name
196
+ it 'exists if explicitly requested' do
197
+ options[:zip] = true
198
+ actual.should be_readable
199
+ end
200
+
201
+ it 'does not exist when excluded' do
202
+ options[:zip] = false
203
+ actual.exist?.should be_false
71
204
  end
72
- contents.should == [ filename.basename.to_s ]
73
205
  end
74
206
 
75
- it 'can be opened by something other than rubyzip' do
76
- `which unzip`
77
- pending "unzip is not available" unless $? == 0
207
+ context do
208
+ before do
209
+ xml # for side effects
210
+ end
78
211
 
79
- `unzip -l '#{expected_zipfile}' 2>&1`.should =~ /#{filename.basename}\s/
80
- $?.should == 0
212
+ it 'contains just the XML file' do
213
+ contents = []
214
+ Zip::ZipFile.foreach(expected_zipfile) do |entry|
215
+ contents << entry.name
216
+ end
217
+ contents.should == [ filename.basename.to_s ]
218
+ end
219
+
220
+ it 'can be opened by something other than rubyzip' do
221
+ `which unzip`
222
+ pending "unzip is not available" unless $? == 0
223
+
224
+ `unzip -l '#{expected_zipfile}' 2>&1`.should =~ /#{filename.basename}\s/
225
+ $?.should == 0
226
+ end
81
227
  end
82
228
  end
83
229