package llmfs
import (
"io"
"testing"
)
func TestAskFile_Read_Empty(t *testing.T) {
mock := NewMockBackend()
ask := NewAskFile(mock)
// Initial read should be empty (EOF)
buf := make([]byte, 100)
n, err := ask.Read(buf, 0)
if err != io.EOF {
t.Errorf("Read() error = %v, want io.EOF", err)
}
if n != 0 {
t.Errorf("Read() n = %d, want 0", n)
}
}
func TestAskFile_WriteRead(t *testing.T) {
mock := NewMockBackend()
mock.askResponse = "Hello, I'm Claude!"
ask := NewAskFile(mock)
// Write a prompt
prompt := "Hello!"
n, err := ask.Write([]byte(prompt), 0)
if err != nil {
t.Fatalf("Write() error: %v", err)
}
if n != len(prompt) {
t.Errorf("Write() n = %d, want %d", n, len(prompt))
}
// Read response
buf := make([]byte, 100)
readN, err := ask.Read(buf, 0)
if err != nil {
t.Fatalf("Read() error: %v", err)
}
response := string(buf[:readN])
expected := "Hello, I'm Claude!\n"
if response != expected {
t.Errorf("Read() = %q, want %q", response, expected)
}
}
func TestAskFile_Write_EmptyNoOp(t *testing.T) {
mock := NewMockBackend()
ask := NewAskFile(mock)
// Empty write should be no-op
n, err := ask.Write([]byte(""), 0)
if err != nil {
t.Fatalf("Write() error: %v", err)
}
if n != 0 {
t.Errorf("Write('') n = %d, want 0", n)
}
// Whitespace-only also no-op
n, err = ask.Write([]byte(" \n\t "), 0)
if err != nil {
t.Fatalf("Write() error: %v", err)
}
}
func TestAskFile_Write_Error(t *testing.T) {
mock := NewMockBackend()
mock.askError = io.ErrUnexpectedEOF
ask := NewAskFile(mock)
// Write should succeed (error is stored for read)
n, err := ask.Write([]byte("test"), 0)
if err != nil {
t.Fatalf("Write() error: %v", err)
}
if n != 4 {
t.Errorf("Write() n = %d, want 4", n)
}
// Read should return the error
buf := make([]byte, 100)
readN, _ := ask.Read(buf, 0)
response := string(buf[:readN])
if response[:6] != "Error:" {
t.Errorf("Read() = %q, should start with 'Error:'", response)
}
}
func TestAskFile_AutoCompaction_BelowThreshold(t *testing.T) {
mock := NewMockBackend()
mock.totalTokens = 100000 // 50% of 200K, below 80% threshold
mock.contextLimit = 200000
mock.askResponse = "response"
ask := NewAskFile(mock)
// Write should NOT trigger compaction
ask.Write([]byte("test"), 0)
if mock.compactCalled {
t.Error("Compact() should not be called below threshold")
}
}
func TestAskFile_AutoCompaction_AboveThreshold(t *testing.T) {
mock := NewMockBackend()
mock.totalTokens = 170000 // 85% of 200K, above 80% threshold
mock.contextLimit = 200000
mock.askResponse = "response"
ask := NewAskFile(mock)
// Write SHOULD trigger compaction
ask.Write([]byte("test"), 0)
if !mock.compactCalled {
t.Error("Compact() should be called when above threshold")
}
}
func TestAskFile_AutoCompaction_ExactThreshold(t *testing.T) {
mock := NewMockBackend()
mock.totalTokens = 160000 // Exactly 80% of 200K
mock.contextLimit = 200000
mock.askResponse = "response"
ask := NewAskFile(mock)
// Write should NOT trigger (we use > not >=)
ask.Write([]byte("test"), 0)
if mock.compactCalled {
t.Error("Compact() should not be called at exactly threshold")
}
}
func TestAskFile_AutoCompaction_JustAboveThreshold(t *testing.T) {
mock := NewMockBackend()
mock.totalTokens = 160001 // Just over 80%
mock.contextLimit = 200000
mock.askResponse = "response"
ask := NewAskFile(mock)
// Write SHOULD trigger compaction
ask.Write([]byte("test"), 0)
if !mock.compactCalled {
t.Error("Compact() should be called when just above threshold")
}
}
func TestAskFile_Stat(t *testing.T) {
mock := NewMockBackend()
mock.askResponse = "Hello!"
ask := NewAskFile(mock)
// Initial stat - no response yet
stat := ask.Stat()
if stat.Length != 0 {
t.Errorf("Stat().Length = %d, want 0 (no response yet)", stat.Length)
}
// Write to get a response
ask.Write([]byte("test"), 0)
// Stat should now show response length (with newline)
stat = ask.Stat()
expected := uint64(len("Hello!\n"))
if stat.Length != expected {
t.Errorf("Stat().Length = %d, want %d", stat.Length, expected)
}
}
func TestAskFile_ResponseNewline(t *testing.T) {
mock := NewMockBackend()
// Response without trailing newline
mock.askResponse = "No newline"
ask := NewAskFile(mock)
ask.Write([]byte("test"), 0)
buf := make([]byte, 100)
n, _ := ask.Read(buf, 0)
response := string(buf[:n])
// Should have newline added
if response[len(response)-1] != '\n' {
t.Errorf("Response should end with newline, got %q", response)
}
// Response with trailing newline already
mock.askResponse = "Has newline\n"
ask2 := NewAskFile(mock)
ask2.Write([]byte("test"), 0)
n, _ = ask2.Read(buf, 0)
response = string(buf[:n])
// Should NOT double the newline
if response != "Has newline\n" {
t.Errorf("Response = %q, should not double newline", response)
}
}
func TestCompactThreshold(t *testing.T) {
// Verify the threshold constant
if CompactThreshold != 0.80 {
t.Errorf("CompactThreshold = %f, want 0.80", CompactThreshold)
}
}