# frozen_string_literal: true # Offline tests for the stereo call-recording splitter (no gems, no fixtures - WAVs are built here). # This handles the only copy of a recorded call, so the tests that matter most are the ones proving it # refuses to guess: wrong channel count, wrong bit depth, not a WAV at all. Mangling audio silently # would be worse than declining to split it. # Run: ruby components/backend/test/test_wav_split.rb require_relative "../lib/helpdesk/wav_split" require "tmpdir" $pass = 0; $fail = 0 def ok(d); $pass += 1; puts " ok #{d}"; end def bad(d, got = nil); $fail += 1; puts " FAIL #{d}#{got.nil? ? '' : " (got: #{got.inspect})"}"; end def eq(a, b, d); a == b ? ok(d) : bad(d, a); end def truthy(x, d); x ? ok(d) : bad(d, x); end def falsy(x, d); x ? bad(d, x) : ok(d); end def raises(d) ; yield; bad(d, "no error"); rescue Helpdesk::WavSplit::Error; ok(d); end W = Helpdesk::WavSplit # Build a stereo WAV from explicit per-channel samples, so the test knows exactly what should come out. def stereo_wav(left, right, rate: 16_000, bits: 16, channels: 2, fmt: 1, extra_chunk: false) pcm = +"" left.zip(right).each { |l, r| pcm << [l].pack("v") << [r].pack("v") } body = +"" body << "fmt " << [16].pack("V") << [fmt].pack("v") << [channels].pack("v") body << [rate].pack("V") << [rate * channels * bits / 8].pack("V") body << [channels * bits / 8].pack("v") << [bits].pack("v") body << "LIST" << [4].pack("V") << "INFO" if extra_chunk body << "data" << [pcm.bytesize].pack("V") << pcm "RIFF" + [4 + body.bytesize].pack("V") + "WAVE" + body end def samples_of(path) raw = File.binread(path) info = W.parse(raw) raw.byteslice(info[:data_offset], info[:data_size]).unpack("v*") end puts "parsing a header" h = W.parse(stereo_wav([1, 2], [3, 4])) eq(h[:channels], 2, "channel count read") eq(h[:sample_rate], 16_000, "sample rate read") eq(h[:bits], 16, "bit depth read") eq(h[:format], 1, "PCM format read") truthy(h[:data_offset] > 0, "data offset located") puts "chunks in any order, unknown ones skipped" h2 = W.parse(stereo_wav([1], [2], extra_chunk: true)) eq(h2[:channels], 2, "a LIST chunk before data does not confuse it") truthy(h2[:data_offset] > 0, "data still found after an unknown chunk") puts "splitting puts each speaker in their own file" Dir.mktmpdir do |dir| src = File.join(dir, "call.wav") # left = operator, right = caller; distinct values so a swap would be obvious File.binwrite(src, stereo_wav([100, 101, 102], [-100, -101, -102])) l = File.join(dir, "op.wav"); r = File.join(dir, "caller.wav") res = W.split(src, left_path: l, right_path: r) eq(res[:frames], 3, "frame count reported") eq(res[:sample_rate], 16_000, "sample rate carried through") eq(samples_of(l), [100, 101, 102], "left channel is the operator, in order") eq(samples_of(r), [65_436, 65_435, 65_434], "right channel is the caller (unsigned view of -100..-102)") eq(W.parse(File.binread(l))[:channels], 1, "output is mono") eq(W.parse(File.binread(r))[:channels], 1, "both outputs are mono") eq(W.parse(File.binread(l))[:sample_rate], 16_000, "output keeps the sample rate") end puts "a truncated recording is split as far as it goes" Dir.mktmpdir do |dir| src = File.join(dir, "cut.wav") full = stereo_wav([1, 2, 3, 4], [5, 6, 7, 8]) # A call that ended abruptly: the header still claims the original length. File.binwrite(src, full.byteslice(0, full.bytesize - 8)) l = File.join(dir, "l.wav"); r = File.join(dir, "r.wav") res = W.split(src, left_path: l, right_path: r) eq(res[:frames], 2, "only the frames actually present are used") eq(samples_of(l), [1, 2], "left truncated cleanly") eq(samples_of(r), [5, 6], "right truncated cleanly") end puts "IT MUST REFUSE what it does not fully understand" Dir.mktmpdir do |dir| l = File.join(dir, "l.wav"); r = File.join(dir, "r.wav") mono = File.join(dir, "mono.wav") File.binwrite(mono, stereo_wav([1, 2], [3, 4], channels: 1)) raises("a mono file is refused, not silently duplicated") { W.split(mono, left_path: l, right_path: r) } deep = File.join(dir, "24bit.wav") File.binwrite(deep, stereo_wav([1], [2], bits: 24)) raises("24-bit audio is refused rather than misread as 16") { W.split(deep, left_path: l, right_path: r) } junk = File.join(dir, "junk.wav") File.binwrite(junk, "this is not audio at all") raises("a non-RIFF file is refused") { W.split(junk, left_path: l, right_path: r) } empty = File.join(dir, "empty.wav") File.binwrite(empty, stereo_wav([], [])) raises("a file with no frames is refused") { W.split(empty, left_path: l, right_path: r) } falsy(File.exist?(l), "nothing is written when the input is rejected") end puts "splittable? answers without raising" Dir.mktmpdir do |dir| good = File.join(dir, "s.wav"); File.binwrite(good, stereo_wav([1], [2])) mono = File.join(dir, "m.wav"); File.binwrite(mono, stereo_wav([1], [2], channels: 1)) junk = File.join(dir, "j.wav"); File.binwrite(junk, "nope") truthy(W.splittable?(good), "stereo PCM is splittable") falsy(W.splittable?(mono), "mono is not") falsy(W.splittable?(junk), "junk is not, and does not raise") falsy(W.splittable?(File.join(dir, "missing.wav")), "a missing file is not, and does not raise") end puts puts "#{$pass} passed, #{$fail} failed" exit($fail.zero? ? 0 : 1)