2ca5ffa4f9
capture_stop held the IPC request open for the whole map reduce, up to twenty minutes. A voice turn that says "хватит" waited for forty model calls before Maven said anything. Stop now returns the transcript and the summary runs on a goroutine in the daemon's WaitGroup, on the daemon context so a client that hung up does not cancel the only readable record of the meeting. With no summary and save_transcript false, writeNotes wrote nothing at all: an hour of meeting left a blob that prunes in seven days and no trace in the note store. The transcript is written instead when the summary is missing. That flag decides whether the verbatim record is kept in addition to a summary, not whether the meeting is remembered. The wire carries the session token now, and the contract comments say what the code does: the summary is usually absent from the stop response, and re running a stored blob is a manual job because no method takes a blob id. The save_transcript comment says the cost is recall corpus rather than disk. Found in review of #73.
119 lines
3.6 KiB
Go
119 lines
3.6 KiB
Go
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)
|
|
}
|
|
}
|