package main import ( "context" "strings" "sync" "testing" "time" "github.com/kami/maven/internal/audio" "github.com/kami/maven/internal/capture" "github.com/kami/maven/internal/config" "github.com/kami/maven/internal/ipc" "github.com/kami/maven/internal/media" ) // silentTranscriber stands in for mavsttd: one fixed phrase per window, so the // wiring can be tested without whisper. type silentTranscriber struct{} func (silentTranscriber) Transcribe(_ context.Context, _ audio.Audio) (string, float64, error) { return "решили купить насос", 1.0, nil } func testCaptureWiring(t *testing.T) (*captureWiring, *sync.WaitGroup) { t.Helper() blobs, err := media.Open(t.TempDir(), 0, 0) if err != nil { t.Fatal(err) } rec, err := capture.New(blobs, silentTranscriber{}, nil, capture.Config{}) if err != nil { t.Fatal(err) } var wg sync.WaitGroup return &captureWiring{ rec: rec, st: newTestStore(t), cfg: &config.CaptureConfig{}, now: time.Now, ctx: context.Background(), wg: &wg, }, &wg } // A frame carrying the wrong token must not land in the running session. Append // and stop used to address "whatever is running now", so a client whose session // had already ended went on recording into somebody else's meeting, and any // client could end a recording it never started. func TestCaptureRefusesAnotherClientsToken(t *testing.T) { c, _ := testCaptureWiring(t) start, err := c.start(context.Background(), ipc.CaptureStartReq{Label: "встреча"}) if err != nil { t.Fatal(err) } if start.Token == "" { t.Fatal("start handed back no session token") } if _, err := c.append(context.Background(), ipc.CaptureAppendReq{ Token: "not-mine", Audio: audio.Audio{Format: audio.PCM16kMono, Bytes: make([]byte, 3200)}, }); err == nil { t.Error("a frame with the wrong token was accepted") } if _, err := c.stop(context.Background(), ipc.CaptureStopReq{Token: "not-mine"}); err == nil { t.Error("a stop with the wrong token ended the session") } if st, _ := c.status(context.Background()); !st.Running { t.Error("the session was ended by a client that does not own it") } } // Stop answers with the transcript and does not wait for the summary. The // summary is up to forty model calls, and holding the IPC request for them meant // the client that said "стоп" sat with no answer for minutes. // // With no summariser wired the note still has to be written, from the transcript. // save_transcript is about keeping the verbatim record IN ADDITION to a summary, // not about whether the meeting is remembered at all — without this fallback a // dead llama-server meant an hour of meeting left no note behind. func TestStopReturnsTranscriptAndNotesItWithoutASummary(t *testing.T) { c, wg := testCaptureWiring(t) start, err := c.start(context.Background(), ipc.CaptureStartReq{Label: "планёрка"}) if err != nil { t.Fatal(err) } if _, err := c.append(context.Background(), ipc.CaptureAppendReq{ Token: start.Token, Audio: audio.Audio{Format: audio.PCM16kMono, Bytes: make([]byte, 32000)}, }); err != nil { t.Fatal(err) } resp, err := c.stop(context.Background(), ipc.CaptureStopReq{Token: start.Token}) if err != nil { t.Fatalf("stop: %v", err) } if resp.Transcript == "" { t.Fatal("stop returned no transcript") } if resp.Summary != "" { t.Errorf("summary = %q, want none inside the request", resp.Summary) } wg.Wait() notes, err := c.st.RecentNotes(context.Background(), 10) if err != nil { t.Fatal(err) } var found bool for _, n := range notes { if strings.Contains(n.Text, "насос") { found = true } } if !found { t.Fatalf("the meeting left no note behind: %+v", notes) } }