tapsoob 0.8.5 → 0.8.7

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.
@@ -2,6 +2,20 @@ require 'spec_helper'
2
2
  require 'tapsoob/schema'
3
3
 
4
4
  RSpec.describe Tapsoob::Schema do
5
+ # SQLite file-based URL helpers — memory: URLs open a fresh DB on each connect,
6
+ # so methods that open their own connection (dump, foreign_keys, indexes, …)
7
+ # must use a file-backed database.
8
+ def with_sqlite_file
9
+ path = File.join(Dir.tmpdir, "tapsoob_schema_#{Process.pid}_#{rand(9999)}.db")
10
+ url = DbHelpers.adapt_url("sqlite://#{path}")
11
+ db = DbHelpers.connect(url)
12
+ db.extension :schema_dumper
13
+ yield url, db
14
+ ensure
15
+ DbHelpers.disconnect_all
16
+ File.delete(path) rescue nil
17
+ end
18
+
5
19
  let(:db) do
6
20
  d = connect_sqlite
7
21
  d.extension :schema_dumper
@@ -99,4 +113,144 @@ RSpec.describe Tapsoob::Schema do
99
113
  }.not_to raise_error
100
114
  end
101
115
  end
116
+
117
+ # ── dump ─────────────────────────────────────────────────────────────────────
118
+
119
+ describe '.dump' do
120
+ it 'returns a migration string covering all tables' do
121
+ with_sqlite_file do |url, tmp|
122
+ tmp.create_table(:things) { primary_key :id; String :name }
123
+ result = described_class.dump(url)
124
+ expect(result).to include('Sequel::Migration')
125
+ expect(result).to include('things')
126
+ end
127
+ end
128
+
129
+ it 'includes both up and down blocks' do
130
+ with_sqlite_file do |url, tmp|
131
+ tmp.create_table(:items) { primary_key :id }
132
+ result = described_class.dump(url)
133
+ expect(result).to include('def up')
134
+ expect(result).to include('def down')
135
+ end
136
+ end
137
+ end
138
+
139
+ # ── foreign_keys ─────────────────────────────────────────────────────────────
140
+
141
+ describe '.foreign_keys' do
142
+ it 'returns a string (even when there are no FK constraints)' do
143
+ with_sqlite_file do |url, _|
144
+ result = described_class.foreign_keys(url)
145
+ expect(result).to be_a(String)
146
+ end
147
+ end
148
+ end
149
+
150
+ # ── indexes ──────────────────────────────────────────────────────────────────
151
+
152
+ describe '.indexes' do
153
+ it 'returns a string' do
154
+ with_sqlite_file do |url, tmp|
155
+ tmp.create_table(:idx_things) { primary_key :id; String :slug }
156
+ tmp.add_index(:idx_things, :slug)
157
+ result = described_class.indexes(url)
158
+ expect(result).to be_a(String)
159
+ end
160
+ end
161
+ end
162
+
163
+ # ── load via URL ─────────────────────────────────────────────────────────────
164
+
165
+ describe '.load (URL path)' do
166
+ it 'creates the table when passed a URL string' do
167
+ with_sqlite_file do |url, tmp|
168
+ schema_str = described_class.dump_table(db, :articles, {})
169
+ described_class.load(url, schema_str)
170
+ expect(tmp.table_exists?(:articles)).to be true
171
+ end
172
+ end
173
+
174
+ it 'drops then recreates table when drop: true and passed a URL' do
175
+ with_sqlite_file do |url, tmp|
176
+ schema_str = described_class.dump_table(db, :articles, {})
177
+ described_class.load(url, schema_str)
178
+ described_class.load(url, schema_str, drop: true)
179
+ expect(tmp.table_exists?(:articles)).to be true
180
+ end
181
+ end
182
+ end
183
+
184
+ # ── load_indexes ─────────────────────────────────────────────────────────────
185
+
186
+ describe '.load_indexes' do
187
+ it 'applies an index migration without error' do
188
+ with_sqlite_file do |url, tmp|
189
+ tmp.create_table(:things) { primary_key :id; String :slug }
190
+ index_migration = <<~RUBY
191
+ Class.new(Sequel::Migration) do
192
+ def up
193
+ add_index :things, :slug
194
+ end
195
+ end
196
+ RUBY
197
+ expect { described_class.load_indexes(url, index_migration) }.not_to raise_error
198
+ expect(tmp.indexes(:things)).to have_key(:things_slug_index)
199
+ end
200
+ end
201
+ end
202
+
203
+ # ── load_foreign_keys ────────────────────────────────────────────────────────
204
+
205
+ describe '.load_foreign_keys' do
206
+ it 'applies a foreign key migration without error' do
207
+ with_sqlite_file do |url, tmp|
208
+ tmp.create_table(:parents) { primary_key :id }
209
+ tmp.create_table(:children) { primary_key :id; Integer :parent_id }
210
+ fk_migration = <<~RUBY
211
+ Class.new(Sequel::Migration) do
212
+ def up
213
+ alter_table(:children) { add_foreign_key [:parent_id], :parents }
214
+ end
215
+ end
216
+ RUBY
217
+ expect { described_class.load_foreign_keys(url, fk_migration) }.not_to raise_error
218
+ end
219
+ end
220
+ end
221
+
222
+ # ── rewrite_non_integer_primary_keys ─────────────────────────────────────────
223
+
224
+ describe '.rewrite_non_integer_primary_keys' do
225
+ it 'leaves integer primary keys unchanged' do
226
+ schema = ' primary_key :id, :type=>"integer"'
227
+ expect(described_class.rewrite_non_integer_primary_keys(schema)).to eq(schema)
228
+ end
229
+
230
+ it 'leaves bigint primary keys unchanged' do
231
+ schema = ' primary_key :id, :type=>"bigint"'
232
+ expect(described_class.rewrite_non_integer_primary_keys(schema)).to eq(schema)
233
+ end
234
+
235
+ it 'rewrites varchar primary keys to column form' do
236
+ schema = ' primary_key :code, :type=>"varchar(10)"'
237
+ result = described_class.rewrite_non_integer_primary_keys(schema)
238
+ expect(result).to include('column :code')
239
+ expect(result).to include('"varchar(10)"')
240
+ expect(result).to include('primary_key: true')
241
+ expect(result).not_to include('primary_key :code,')
242
+ end
243
+
244
+ it 'rewrites uuid primary keys to column form' do
245
+ schema = ' primary_key :id, :type=>"uuid"'
246
+ result = described_class.rewrite_non_integer_primary_keys(schema)
247
+ expect(result).to include('column :id')
248
+ expect(result).to include('primary_key: true')
249
+ end
250
+
251
+ it 'passes through schema with no primary_key lines unchanged' do
252
+ schema = " String :name, size: 50\n Integer :score"
253
+ expect(described_class.rewrite_non_integer_primary_keys(schema)).to eq(schema)
254
+ end
255
+ end
102
256
  end
@@ -220,6 +220,70 @@ RSpec.describe Tapsoob::Utils do
220
220
  end
221
221
  end
222
222
 
223
+ # ── windows? / bin ───────────────────────────────────────────────────────────
224
+
225
+ describe '.windows? / .bin' do
226
+ it 'returns a boolean for windows?' do
227
+ expect([true, false]).to include(described_class.windows?)
228
+ end
229
+
230
+ it 'returns the command unchanged on non-Windows' do
231
+ allow(described_class).to receive(:windows?).and_return(false)
232
+ expect(described_class.bin("mytool")).to eq("mytool")
233
+ end
234
+
235
+ it 'appends .cmd on Windows' do
236
+ allow(described_class).to receive(:windows?).and_return(true)
237
+ expect(described_class.bin("mytool")).to eq("mytool.cmd")
238
+ end
239
+ end
240
+
241
+ # ── incorrect_blobs ──────────────────────────────────────────────────────────
242
+
243
+ describe '.incorrect_blobs' do
244
+ let(:db) do
245
+ d = connect_sqlite
246
+ d.create_table(:blob_test) { primary_key :id; File :data; String :name }
247
+ d
248
+ end
249
+ after { db.disconnect }
250
+
251
+ it 'returns blob-typed column names' do
252
+ expect(described_class.incorrect_blobs(db, :blob_test)).to include(:data)
253
+ end
254
+
255
+ it 'does not include non-blob columns' do
256
+ expect(described_class.incorrect_blobs(db, :blob_test)).not_to include(:name)
257
+ end
258
+ end
259
+
260
+ # ── encode_blobs ASCII-8BIT branch ──────────────────────────────────────────
261
+
262
+ describe '.encode_blobs (ASCII-8BIT branch)' do
263
+ it 'encodes raw binary strings that are not Sequel::SQL::Blob' do
264
+ raw = "binary".force_encoding(Encoding::ASCII_8BIT)
265
+ row = { data: raw }
266
+ described_class.encode_blobs(row, [:data])
267
+ expect(row[:data]).to eq(described_class.base64encode(raw))
268
+ end
269
+ end
270
+
271
+ # ── load_schema with a DB object ─────────────────────────────────────────────
272
+
273
+ describe '.load_schema' do
274
+ let(:dir) { Dir.mktmpdir }
275
+ after { FileUtils.rm_rf(dir) }
276
+
277
+ it 'accepts a Sequel::Database object directly' do
278
+ db = connect_sqlite
279
+ FileUtils.mkdir_p(File.join(dir, 'schemas'))
280
+ schema_content = "Sequel.migration { change { create_table(:load_test) { primary_key :id } } }"
281
+ File.write(File.join(dir, 'schemas', 'load_test.rb'), schema_content)
282
+ expect { described_class.load_schema(dir, db, :load_test) }.not_to raise_error
283
+ db.disconnect
284
+ end
285
+ end
286
+
223
287
  # ── export_rows / export_schema / export_indexes (filesystem) ────────────────
224
288
 
225
289
  describe 'filesystem export helpers' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tapsoob
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.5
4
+ version: 0.8.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Félix Bellanger
@@ -131,11 +131,21 @@ files:
131
131
  - spec/spec_helper.rb
132
132
  - spec/support/db_helpers.rb
133
133
  - spec/support/fixtures.rb
134
+ - spec/support/operation_helpers.rb
134
135
  - spec/support/round_trip_helper.rb
135
136
  - spec/support/shared_examples/round_trip.rb
137
+ - spec/unit/tapsoob/base_spec.rb
136
138
  - spec/unit/tapsoob/chunksize_spec.rb
139
+ - spec/unit/tapsoob/cli_pipeline_spec.rb
140
+ - spec/unit/tapsoob/config_spec.rb
137
141
  - spec/unit/tapsoob/data_stream_spec.rb
142
+ - spec/unit/tapsoob/file_partition_spec.rb
143
+ - spec/unit/tapsoob/keyed_spec.rb
138
144
  - spec/unit/tapsoob/operation_base_spec.rb
145
+ - spec/unit/tapsoob/progress_event_spec.rb
146
+ - spec/unit/tapsoob/progress_spec.rb
147
+ - spec/unit/tapsoob/pull_spec.rb
148
+ - spec/unit/tapsoob/push_spec.rb
139
149
  - spec/unit/tapsoob/schema_spec.rb
140
150
  - spec/unit/tapsoob/utils_spec.rb
141
151
  - spec/unit/tapsoob/version_spec.rb