feat: upload audio, get a private Swedish transcript, discuss it (#33, ADR-033) #39

Merged
mathias merged 299 commits from feat/33-audio-upload-transcript into main 2026-10-03 14:13:37 +00:00
Showing only changes of commit ad071d44cb - Show all commits
+75 -2
View File
@@ -15,6 +15,7 @@ func clearUploadEnv(t *testing.T) {
"TAPIR_TRANSCRIBE_MODEL", "TAPIR_TRANSCRIBE_FALLBACK_MODEL", "TAPIR_TRANSCRIBE_TIMEOUT",
"TAPIR_UPLOAD_TMP_DIR", "TAPIR_FFMPEG_BIN", "TAPIR_FFPROBE_BIN",
"TAPIR_MAX_UPLOADED_TRANSCRIPT_CHARS", "TAPIR_UPLOAD_CHAT_MODEL",
"TAPIR_RECONCILE_BASE_MODEL", "TAPIR_RECONCILE_MODEL", "TAPIR_RECONCILE_CHECK_ORDER",
} {
unset(t, k)
}
@@ -43,8 +44,11 @@ func TestLoad_UploadDefaults(t *testing.T) {
if c.TranscribeFallbackModel != "berget/kb-whisper" {
t.Errorf("TranscribeFallbackModel = %q", c.TranscribeFallbackModel)
}
if c.TranscribeTimeout != 30*time.Minute {
t.Errorf("TranscribeTimeout = %v, want 30m (a 90 min recording took ~7.5 min)", c.TranscribeTimeout)
if c.TranscribeTimeout != 60*time.Minute {
t.Errorf("TranscribeTimeout = %v, want 60m (ADR-034: two engines plus ~300 adjudications)", c.TranscribeTimeout)
}
if c.ReconcileBaseModel != "" || c.ReconcileModel != "" || c.ReconcileCheckOrder {
t.Errorf("reconciliation must be OFF by default: %q %q %v", c.ReconcileBaseModel, c.ReconcileModel, c.ReconcileCheckOrder)
}
if c.UploadTmpDir != os.TempDir() {
t.Errorf("UploadTmpDir = %q, want os.TempDir()", c.UploadTmpDir)
@@ -139,3 +143,72 @@ func TestLoad_UploadRejectsInvalidValues(t *testing.T) {
})
}
}
func TestLoad_ReconcileSettingsAreParsed(t *testing.T) {
clearUploadEnv(t)
setEnv(t, map[string]string{
"TAPIR_RECONCILE_BASE_MODEL": "iguana/pianissimo",
"TAPIR_RECONCILE_MODEL": "iguana/gemma4-31b",
"TAPIR_RECONCILE_CHECK_ORDER": "true",
})
c, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
if c.ReconcileBaseModel != "iguana/pianissimo" || c.ReconcileModel != "iguana/gemma4-31b" || !c.ReconcileCheckOrder {
t.Errorf("not applied: %+v", c.UploadConfig)
}
}
func TestLoad_ReconcileRefusesAnUnsafeOrIncompleteSetup(t *testing.T) {
tests := []struct {
name string
env map[string]string
}{
{"a base engine with no adjudicator", map[string]string{"TAPIR_RECONCILE_BASE_MODEL": "iguana/pianissimo"}},
{"a cloud adjudicator: private meeting content must not reach it", map[string]string{
"TAPIR_RECONCILE_BASE_MODEL": "iguana/pianissimo", "TAPIR_RECONCILE_MODEL": "berget/mistral-small",
}},
{"a cloud adjudicator from another provider", map[string]string{
"TAPIR_RECONCILE_BASE_MODEL": "iguana/pianissimo", "TAPIR_RECONCILE_MODEL": "gemini/gemini-2.5-pro",
}},
{"an adjudicator alias with no host prefix", map[string]string{
"TAPIR_RECONCILE_BASE_MODEL": "iguana/pianissimo", "TAPIR_RECONCILE_MODEL": "gemma4-26b",
}},
{"a cloud base engine", map[string]string{
"TAPIR_RECONCILE_BASE_MODEL": "berget/kb-whisper", "TAPIR_RECONCILE_MODEL": "iguana/gemma4-26b",
}},
{"the same engine twice", map[string]string{
"TAPIR_RECONCILE_BASE_MODEL": "iguana/kb-whisper", "TAPIR_RECONCILE_MODEL": "iguana/gemma4-26b",
}},
{"a bad order-check flag", map[string]string{
"TAPIR_RECONCILE_BASE_MODEL": "iguana/pianissimo", "TAPIR_RECONCILE_MODEL": "iguana/gemma4-26b", "TAPIR_RECONCILE_CHECK_ORDER": "perhaps",
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
clearUploadEnv(t)
setEnv(t, tt.env)
if _, err := Load(); err == nil {
t.Errorf("Load accepted %v", tt.env)
}
})
}
}
func TestLoad_AnAdjudicatorWithoutABaseEngineIsIgnoredNotAnError(t *testing.T) {
clearUploadEnv(t)
t.Setenv("TAPIR_RECONCILE_MODEL", "iguana/gemma4-26b")
c, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
if c.ReconcileBaseModel != "" {
t.Errorf("reconciliation must stay off without a base engine")
}
}