156 lines
6.0 KiB
Go
156 lines
6.0 KiB
Go
package utils
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
)
|
|
|
|
func TestStreamTerminalDetectorObserveChunkSSEFinishReasonAcrossChunks(t *testing.T) {
|
|
detector := &StreamTerminalDetector{}
|
|
|
|
chunks := [][]byte{
|
|
[]byte("data: {\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"hi\"}]},"),
|
|
[]byte("\"finishReason\":\"STOP\"}]}\n\n"),
|
|
}
|
|
|
|
if detector.ObserveChunk(chunks[0]) {
|
|
t.Fatalf("unexpected terminal detection on first chunk")
|
|
}
|
|
if !detector.ObserveChunk(chunks[1]) {
|
|
t.Fatalf("expected terminal detection for candidates finishReason")
|
|
}
|
|
}
|
|
|
|
func TestStreamTerminalDetectorObserveChunkSSETopLevelFinishReasonAcrossChunks(t *testing.T) {
|
|
detector := &StreamTerminalDetector{}
|
|
chunks := [][]byte{
|
|
[]byte("data: {\"id\":\"abc\","),
|
|
[]byte("\"finishReason\":\"STOP\"}\n\n"),
|
|
}
|
|
if detector.ObserveChunk(chunks[0]) {
|
|
t.Fatalf("unexpected terminal detection on first chunk")
|
|
}
|
|
if !detector.ObserveChunk(chunks[1]) {
|
|
t.Fatalf("expected terminal detection for top-level finishReason")
|
|
}
|
|
}
|
|
|
|
func TestStreamTerminalDetectorObserveChunkDoneMarker(t *testing.T) {
|
|
detector := &StreamTerminalDetector{}
|
|
if !detector.ObserveChunk([]byte("data: [DONE]\n\n")) {
|
|
t.Fatalf("expected [DONE] marker to be terminal")
|
|
}
|
|
}
|
|
|
|
func TestStreamTerminalDetectorObserveChunkDoneMarkerCRLF(t *testing.T) {
|
|
detector := &StreamTerminalDetector{}
|
|
if !detector.ObserveChunk([]byte("data: [DONE]\r\n\r\n")) {
|
|
t.Fatalf("expected [DONE] marker with CRLF delimiter to be terminal")
|
|
}
|
|
}
|
|
|
|
func TestStreamTerminalDetectorObserveChunkIgnoresUnspecifiedFinishReason(t *testing.T) {
|
|
detector := &StreamTerminalDetector{}
|
|
if detector.ObserveChunk([]byte("data: {\"finishReason\":\"FINISH_REASON_UNSPECIFIED\"}\n\n")) {
|
|
t.Fatalf("unexpected terminal detection for FINISH_REASON_UNSPECIFIED")
|
|
}
|
|
}
|
|
|
|
func TestStreamTerminalDetectorObserveChunkPlainJSONAcrossChunks(t *testing.T) {
|
|
detector := &StreamTerminalDetector{}
|
|
if detector.ObserveChunk([]byte("{\"content\":\"hello\",")) {
|
|
t.Fatalf("unexpected terminal detection for incomplete json")
|
|
}
|
|
if !detector.ObserveChunk([]byte("\"finishReason\":\"STOP\"}")) {
|
|
t.Fatalf("expected terminal detection for plain json stream")
|
|
}
|
|
}
|
|
|
|
func TestStreamTerminalDetectorObserveChunkJSONWithDataURITokenAndDelimiter(t *testing.T) {
|
|
detector := &StreamTerminalDetector{}
|
|
chunk := []byte("{\"finishReason\":\"STOP\",\"content\":{\"parts\":[{\"inlineData\":{\"mimeType\":\"image/png\",\"data\":\"data:image/png;base64,AAAA\"}}]}}\n\n")
|
|
if !detector.ObserveChunk(chunk) {
|
|
t.Fatalf("expected terminal detection for delimited JSON containing data URI token")
|
|
}
|
|
}
|
|
|
|
func TestStreamTerminalDetectorObserveChunkMultiEventSSEInSingleChunk(t *testing.T) {
|
|
detector := &StreamTerminalDetector{}
|
|
chunk := []byte("data: {}\n\ndata: {\"finishReason\":\"STOP\"}\n\n")
|
|
if !detector.ObserveChunk(chunk) {
|
|
t.Fatalf("expected terminal detection for multi-event SSE chunk")
|
|
}
|
|
}
|
|
|
|
func TestStreamTerminalDetectorObserveChunkMetadataOnlyFrameIsNotTerminal(t *testing.T) {
|
|
detector := &StreamTerminalDetector{}
|
|
if detector.ObserveChunk([]byte("data: {\"usageMetadata\":{\"totalTokenCount\":12}}\n\n")) {
|
|
t.Fatalf("unexpected terminal detection for metadata-only frame")
|
|
}
|
|
}
|
|
|
|
func TestStreamTerminalDetectorObserveChunkMetadataWithFinishedCandidateIsTerminal(t *testing.T) {
|
|
detector := &StreamTerminalDetector{}
|
|
if !detector.ObserveChunk([]byte("data: {\"usageMetadata\":{\"totalTokenCount\":12},\"candidates\":[{\"finishReason\":\"STOP\"}]}\n\n")) {
|
|
t.Fatalf("expected terminal detection for metadata with finished candidate")
|
|
}
|
|
}
|
|
|
|
func TestStreamTerminalDetectorObserveChunkMetadataWithUnspecifiedCandidateIsNotTerminal(t *testing.T) {
|
|
detector := &StreamTerminalDetector{}
|
|
if detector.ObserveChunk([]byte("data: {\"usageMetadata\":{\"totalTokenCount\":12},\"candidates\":[{\"finishReason\":\"FINISH_REASON_UNSPECIFIED\"}]}\n\n")) {
|
|
t.Fatalf("unexpected terminal detection for metadata with unfinished candidate")
|
|
}
|
|
}
|
|
|
|
func TestStreamTerminalDetectorObserveChunkMetadataWithMixedCandidatesIsNotTerminal(t *testing.T) {
|
|
detector := &StreamTerminalDetector{}
|
|
if detector.ObserveChunk([]byte("data: {\"usageMetadata\":{\"totalTokenCount\":12},\"candidates\":[{\"finishReason\":\"STOP\"},{}]}\n\n")) {
|
|
t.Fatalf("unexpected terminal detection for metadata with mixed finished/unfinished candidates")
|
|
}
|
|
}
|
|
|
|
func TestStreamTerminalDetectorObserveChunkTopLevelArrayWithCandidatesFinishReason(t *testing.T) {
|
|
detector := &StreamTerminalDetector{}
|
|
chunk := []byte("data: [{\"candidates\":[{\"finishReason\":\"STOP\"}]}]\n\n")
|
|
if !detector.ObserveChunk(chunk) {
|
|
t.Fatalf("expected terminal detection for top-level array payload")
|
|
}
|
|
}
|
|
|
|
func TestStreamTerminalDetectorObserveChunkCandidatesRequireAllFinished(t *testing.T) {
|
|
detector := &StreamTerminalDetector{}
|
|
chunk := []byte("data: {\"candidates\":[{\"finishReason\":\"STOP\"},{\"finishReason\":\"FINISH_REASON_UNSPECIFIED\"}]}\n\n")
|
|
if detector.ObserveChunk(chunk) {
|
|
t.Fatalf("unexpected terminal detection when not all candidates are finished")
|
|
}
|
|
}
|
|
|
|
func TestStreamTerminalDetectorObserveChunkCandidatesAllFinishedIsTerminal(t *testing.T) {
|
|
detector := &StreamTerminalDetector{}
|
|
chunk := []byte("data: {\"candidates\":[{\"finishReason\":\"STOP\"},{\"finishReason\":\"MAX_TOKENS\"}]}\n\n")
|
|
if !detector.ObserveChunk(chunk) {
|
|
t.Fatalf("expected terminal detection when all candidates are finished")
|
|
}
|
|
}
|
|
|
|
func TestStreamTerminalDetectorObserveChunkUndelimitedOverflowKeepsPrefix(t *testing.T) {
|
|
detector := &StreamTerminalDetector{}
|
|
originalPrefix := bytes.Repeat([]byte("a"), maxTerminalDetectorBufferBytes/2)
|
|
chunk := append([]byte(nil), originalPrefix...)
|
|
chunk = append(chunk, bytes.Repeat([]byte("b"), maxTerminalDetectorBufferBytes)...)
|
|
|
|
if detector.ObserveChunk(chunk) {
|
|
t.Fatalf("unexpected terminal detection for non-json buffer")
|
|
}
|
|
|
|
trimTo := maxTerminalDetectorBufferBytes / 2
|
|
got := detector.pending.Bytes()
|
|
if len(got) != trimTo {
|
|
t.Fatalf("expected pending length %d, got %d", trimTo, len(got))
|
|
}
|
|
if !bytes.Equal(got, originalPrefix) {
|
|
t.Fatalf("expected pending buffer to keep original prefix")
|
|
}
|
|
}
|