package ipc import ( "context" "errors" "testing" "time" "github.com/kami/maven/internal/audio" ) // The load-bearing default for the most invasive capability Maven has: on a core // that was never configured to record, there is no wire path that starts a // recording, feeds one, or harvests one. Every one of the four methods refuses. func TestCapture_OffUnlessConfigured(t *testing.T) { _, _, cli, _ := newServerWithStore(t) ctx := context.Background() if _, err := cli.CaptureStart(ctx, CaptureStartReq{Label: "встреча"}); !errors.Is(err, ErrUnknownMethod) { t.Errorf("CaptureStart error = %v, want ErrUnknownMethod", err) } if _, err := cli.CaptureAppend(ctx, CaptureAppendReq{}); !errors.Is(err, ErrUnknownMethod) { t.Errorf("CaptureAppend error = %v, want ErrUnknownMethod", err) } if _, err := cli.CaptureStop(ctx, CaptureStopReq{}); !errors.Is(err, ErrUnknownMethod) { t.Errorf("CaptureStop error = %v, want ErrUnknownMethod", err) } if _, err := cli.CaptureStatus(ctx); !errors.Is(err, ErrUnknownMethod) { t.Errorf("CaptureStatus error = %v, want ErrUnknownMethod", err) } } // With the hooks wired, a whole session crosses the boundary intact: the label // out, the audio in, the summary back. func TestCapture_RoundTrip(t *testing.T) { _, srv, cli, _ := newServerWithStore(t) ctx := context.Background() started := time.Now().UTC().Truncate(time.Second) var gotLabel string var gotBytes int var gotDiscard bool srv.CaptureStartFn = func(_ context.Context, req CaptureStartReq) (CaptureStartResp, error) { gotLabel = req.Label return CaptureStartResp{Label: req.Label, Started: started, MaxSeconds: 7200}, nil } srv.CaptureAppendFn = func(_ context.Context, req CaptureAppendReq) (CaptureAppendResp, error) { gotBytes = len(req.Audio.Bytes) return CaptureAppendResp{Seconds: 1.5}, nil } srv.CaptureStopFn = func(_ context.Context, req CaptureStopReq) (CaptureStopResp, error) { gotDiscard = req.Discard return CaptureStopResp{BlobID: "abc", Summary: "— решили купить насос", Chunks: 1}, nil } srv.CaptureStatusFn = func(context.Context) (CaptureStatusResp, error) { return CaptureStatusResp{Running: true, Label: "встреча", Seconds: 1.5}, nil } start, err := cli.CaptureStart(ctx, CaptureStartReq{Label: "встреча с подрядчиком"}) if err != nil { t.Fatalf("CaptureStart: %v", err) } if gotLabel != "встреча с подрядчиком" || start.MaxSeconds != 7200 { t.Errorf("start = %+v (label seen: %q)", start, gotLabel) } if !start.Started.Equal(started) { t.Errorf("started = %v, want %v", start.Started, started) } // Audio must survive the JSON round trip byte for byte — a base64 mistake // here would be silence in the transcript, not a visible error. pcm := []byte{1, 2, 3, 4, 5, 6, 7, 8} ap, err := cli.CaptureAppend(ctx, CaptureAppendReq{ Audio: audio.Audio{Format: audio.PCM16kMono, Bytes: pcm}, }) if err != nil { t.Fatalf("CaptureAppend: %v", err) } if gotBytes != len(pcm) { t.Errorf("%d bytes arrived, sent %d", gotBytes, len(pcm)) } if ap.Seconds != 1.5 || ap.Expired { t.Errorf("append resp = %+v", ap) } st, err := cli.CaptureStatus(ctx) if err != nil { t.Fatalf("CaptureStatus: %v", err) } if !st.Running || st.Label != "встреча" { t.Errorf("status = %+v", st) } stop, err := cli.CaptureStop(ctx, CaptureStopReq{}) if err != nil { t.Fatalf("CaptureStop: %v", err) } if gotDiscard { t.Error("a plain stop arrived as a discard") } if stop.BlobID != "abc" || stop.Summary == "" { t.Errorf("stop = %+v", stop) } } // "забудь, не записывай" has to reach core as a discard, not as an ordinary // stop that quietly keeps everything. func TestCapture_DiscardCrossesTheWire(t *testing.T) { _, srv, cli, _ := newServerWithStore(t) var gotDiscard bool srv.CaptureStopFn = func(_ context.Context, req CaptureStopReq) (CaptureStopResp, error) { gotDiscard = req.Discard return CaptureStopResp{Discarded: req.Discard}, nil } resp, err := cli.CaptureStop(context.Background(), CaptureStopReq{Discard: true}) if err != nil { t.Fatalf("CaptureStop: %v", err) } if !gotDiscard || !resp.Discarded { t.Errorf("discard lost: sent true, core saw %v, resp %+v", gotDiscard, resp) } }