zot/packages/core/core_test.go
patriceckhart fa7d8d8be5 refactor: split source into packages/{provider,core,tui,agent}
Single Go module, four top-level packages under packages/. Import
paths become github.com/patriceckhart/zot/packages/<name>; downstream
consumers can depend on individual packages without pulling the rest.

Layout:
  packages/provider/     LLM clients + catalog
  packages/provider/auth/ credential store + OAuth + login server
  packages/core/         agent loop, sessions, cost
  packages/tui/          terminal toolkit + chat view
  packages/agent/        CLI wiring, system prompt
    extensions/ extproto/ modes/ tools/ skills/ swarm/
    sdk/  (was pkg/zotcore, package renamed zotcore -> sdk)
    ext/  (was pkg/zotext, package renamed zotext -> ext)

internal/ and pkg/ removed. The internal/assets logo moved into
packages/provider/auth/assets.

Public Go SDK identifiers renamed:
  pkg/zotcore (package zotcore) -> packages/agent/sdk (package sdk)
  pkg/zotext  (package zotext)  -> packages/agent/ext (package ext)

This breaks Go-based extensions and embedders; the JSON wire protocol
for extensions and RPC is unchanged, so non-Go extensions, already-
built extension binaries, and zot rpc consumers are unaffected.

Docs, examples, and the built-in write-zot-extension skill updated
for the new paths and identifiers. Shadow-bug fixes in code samples
(ext := ext.New -> e := ext.New).
2026-05-27 09:07:15 +02:00

107 lines
3.1 KiB
Go

package core
import (
"os"
"testing"
"time"
"github.com/patriceckhart/zot/packages/provider"
)
func TestSessionRoundTrip(t *testing.T) {
dir := t.TempDir()
os.Setenv("ZOT_HOME", dir)
sess, err := NewSession(dir, "/tmp/project", "anthropic", "claude-sonnet-4-5", "test")
if err != nil {
t.Fatal(err)
}
msg := provider.Message{
Role: provider.RoleUser,
Content: []provider.Content{
provider.TextBlock{Text: "hello"},
},
Time: time.Now().UTC(),
}
if err := sess.AppendMessage(msg); err != nil {
t.Fatal(err)
}
if err := sess.Close(); err != nil {
t.Fatal(err)
}
reopened, msgs, err := OpenSession(sess.Path)
if err != nil {
t.Fatal(err)
}
// OpenSession returns a live append writer; close it before t.TempDir
// runs cleanup, otherwise windows refuses to delete the open file.
t.Cleanup(func() { _ = reopened.Close() })
if len(msgs) != 1 {
t.Fatalf("got %d messages", len(msgs))
}
tb, ok := msgs[0].Content[0].(provider.TextBlock)
if !ok || tb.Text != "hello" {
t.Fatalf("got %+v", msgs[0])
}
}
// TestNewSessionAtPathCreatesAtExplicitPath proves the swarm child's
// session-persistence fallback works: NewSessionAtPath creates the
// file (and parent dirs) at the exact path the caller chose,
// independent of SessionsDir(root, cwd). Without this helper, the
// swarm child's --session <path> with a non-existent path would
// fall through to NewSession and write the session under a
// different (autogenerated) name, leaving the supervisor-chosen
// path empty.
func TestNewSessionAtPathCreatesAtExplicitPath(t *testing.T) {
dir := t.TempDir()
want := dir + "/nested/sub/session.json"
s, err := NewSessionAtPath(want, "/tmp/proj", "anthropic", "claude", "test")
if err != nil {
t.Fatalf("NewSessionAtPath: %v", err)
}
if s.Path != want {
t.Errorf("Path = %q; want %q", s.Path, want)
}
if err := s.AppendMessage(provider.Message{
Role: provider.RoleUser,
Content: []provider.Content{provider.TextBlock{Text: "hi"}},
Time: time.Now().UTC(),
}); err != nil {
t.Fatal(err)
}
if err := s.Close(); err != nil {
t.Fatal(err)
}
// Reopen at the same path and the message must still be there.
reopened, msgs, err := OpenSession(want)
if err != nil {
t.Fatalf("OpenSession at fixed path: %v", err)
}
t.Cleanup(func() { _ = reopened.Close() })
if len(msgs) != 1 {
t.Fatalf("reopen got %d msgs; want 1", len(msgs))
}
// A second call to NewSessionAtPath at the same path must fail
// (O_EXCL): callers should use OpenSession to reattach to an
// existing file.
if _, err := NewSessionAtPath(want, "/tmp/proj", "anthropic", "claude", "test"); err == nil {
t.Error("NewSessionAtPath on existing path returned nil; want error")
}
}
func TestCostAdd(t *testing.T) {
var c CostTracker
c.Add(provider.Usage{InputTokens: 100, OutputTokens: 50, CostUSD: 0.01})
c.Add(provider.Usage{InputTokens: 200, OutputTokens: 25, CostUSD: 0.02})
if c.Total.InputTokens != 300 || c.Total.OutputTokens != 75 {
t.Fatalf("got %+v", c.Total)
}
if c.Total.CostUSD < 0.0299 || c.Total.CostUSD > 0.0301 {
t.Fatalf("got cost %v", c.Total.CostUSD)
}
}