Prd/components/transcription-worker/test_transcriber.rb
Lucy Doupalů be9f14ce34 Helpdesk - operator console + patched GrapheneOS Dialer for call handling
A small helpdesk system: an office Pixel running a patched GrapheneOS Dialer
answers technician calls, records both call legs as separate channels, and a
Ruby backend transcribes them through Whisper and files an AI summary against
the caller.

Squashed to a single commit for sharing. No credentials are included; secrets
live outside the repo in /etc/helpdesk/env on the server or a gitignored
.claude/env.local locally. See .claude/env.local.example for the shape.

Start at README.md, then docs/architecture.md.
2026-07-27 18:50:32 +02:00

61 lines
2.5 KiB
Ruby

# frozen_string_literal: true
# Zero-dependency test for the Transcriber wrapper. Uses tiny Ruby STUB scripts in place of the
# real transcribe.py, so the subprocess/exit-code/output logic is validated in milliseconds — no
# 7 GB model, no CPU-minutes. (A real end-to-end run is a separate manual smoke.)
# Run: ruby components/transcription-worker/test_transcriber.rb
require_relative "transcriber"
require "tmpdir"
$pass = 0; $fail = 0
def ok(d); $pass += 1; puts " ok #{d}"; end
def bad(d, g = nil); $fail += 1; puts " FAIL #{d}#{g.nil? ? '' : " (#{g.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
Dir.mktmpdir do |dir|
# a stub that writes a [Speaker N] transcript to the -o path and exits with a chosen code
write_stub = lambda do |name, ruby|
p = File.join(dir, name); File.write(p, ruby); p
end
ok_stub = write_stub.call("ok.rb", <<~RB)
o = ARGV[ARGV.index("-o") + 1]
File.write(o, "[Speaker 1]: Ahoj.\\n[Speaker 2]: Nazdar.\\n")
exit 0
RB
empty_stub = write_stub.call("empty.rb", "o=ARGV[ARGV.index('-o')+1]; File.write(o,''); exit 0\n")
gated_stub = write_stub.call("gated.rb", "exit 5\n") # HF terms not accepted (terminal)
crash_stub = write_stub.call("crash.rb", "exit 137\n") # OOM-kill style (retryable)
slow_stub = write_stub.call("slow.rb", "sleep 5\n") # to exercise the timeout
tr = ->(stub, **kw) { Helpdesk::Transcriber.new(python: "ruby", script: stub, env_file: File.join(dir, ".none")).transcribe(File.join(dir, "audio.wav"), **kw) }
puts "success path"
r = tr.call(ok_stub)
eq(r[:state], "transcribed", "exit 0 with output → transcribed")
truthy(r[:transcript].include?("Ahoj"), "transcript captured from -o file")
eq(r[:retryable], false, "success not retryable")
puts "no-speech (empty output on exit 0)"
eq(tr.call(empty_stub)[:state], "no_speech", "empty transcript → no_speech (not a failure)")
puts "terminal config error"
r = tr.call(gated_stub)
eq(r[:state], "config_error", "exit 5 → config_error")
eq(r[:retryable], false, "config error NOT retried")
puts "retryable crash"
r = tr.call(crash_stub)
eq(r[:state], "failed", "exit 137 → failed")
eq(r[:retryable], true, "crash/OOM IS retryable")
puts "timeout kills the process and is retryable"
r = tr.call(slow_stub, timeout: 1)
eq(r[:state], "failed", "timeout → failed")
eq(r[:retryable], true, "timeout is retryable")
eq(r[:reason], "timeout", "reason=timeout")
end
puts "\n#{$pass} passed, #{$fail} failed"
exit($fail.zero? ? 0 : 1)