Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 01c6977a5c |
@@ -1,163 +0,0 @@
|
||||
package handlers
|
||||
|
||||
// a2a_proxy_helpers_test.go — unit tests for extractToolTrace (the only
|
||||
// untested pure function in a2a_proxy_helpers.go). The function parses JSON
|
||||
// so tests use real JSON without any DB or HTTP mocking.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/Molecule-AI/molecule-monorepo/platform/internal/db"
|
||||
)
|
||||
|
||||
// TestExtractToolTrace_HappyPath verifies that a well-formed JSON-RPC result
|
||||
// with a metadata.tool_trace field returns it as json.RawMessage.
|
||||
func TestExtractToolTrace_HappyPath(t *testing.T) {
|
||||
trace := json.RawMessage(`[{"tool":"bash","input":"ls"}]`)
|
||||
resp := map[string]interface{}{
|
||||
"result": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"tool_trace": trace,
|
||||
},
|
||||
},
|
||||
}
|
||||
body, _ := json.Marshal(resp)
|
||||
got := extractToolTrace(body)
|
||||
if got == nil {
|
||||
t.Fatal("extractToolTrace returned nil, expected the trace")
|
||||
}
|
||||
var parsed []map[string]interface{}
|
||||
if err := json.Unmarshal(got, &parsed); err != nil {
|
||||
t.Fatalf("returned value is not valid JSON: %v", err)
|
||||
}
|
||||
if len(parsed) != 1 || parsed[0]["tool"] != "bash" {
|
||||
t.Errorf("unexpected trace content: %v", parsed)
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtractToolTrace_ResultUsageShape tests a result object that has usage
|
||||
// (common A2A response shape) but no tool_trace — should return nil.
|
||||
func TestExtractToolTrace_ResultHasUsageNoTrace(t *testing.T) {
|
||||
resp := map[string]interface{}{
|
||||
"result": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"usage": map[string]int64{"input_tokens": 100, "output_tokens": 200},
|
||||
},
|
||||
},
|
||||
}
|
||||
body, _ := json.Marshal(resp)
|
||||
if got := extractToolTrace(body); got != nil {
|
||||
t.Errorf("expected nil when no tool_trace, got: %s", string(got))
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtractToolTrace_NoResultKey verifies that a response without a "result"
|
||||
// key returns nil.
|
||||
func TestExtractToolTrace_NoResultKey(t *testing.T) {
|
||||
resp := map[string]interface{}{
|
||||
"error": map[string]string{"code": "-32600", "message": "Invalid Request"},
|
||||
}
|
||||
body, _ := json.Marshal(resp)
|
||||
if got := extractToolTrace(body); got != nil {
|
||||
t.Errorf("expected nil for error response, got: %s", string(got))
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtractToolTrace_ResultNotAnObject verifies that a result that is not
|
||||
// a JSON object (e.g., null) returns nil without panicking.
|
||||
func TestExtractToolTrace_ResultNotAnObject(t *testing.T) {
|
||||
body := []byte(`{"result": null}`)
|
||||
if got := extractToolTrace(body); got != nil {
|
||||
t.Errorf("expected nil for null result, got: %s", string(got))
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtractToolTrace_NoMetadata verifies that a result object without
|
||||
// metadata returns nil.
|
||||
func TestExtractToolTrace_NoMetadata(t *testing.T) {
|
||||
resp := map[string]interface{}{
|
||||
"result": map[string]interface{}{
|
||||
"message": "hello",
|
||||
},
|
||||
}
|
||||
body, _ := json.Marshal(resp)
|
||||
if got := extractToolTrace(body); got != nil {
|
||||
t.Errorf("expected nil for result without metadata, got: %s", string(got))
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtractToolTrace_MetadataNotAnObject verifies that a metadata field that
|
||||
// is not a JSON object returns nil without panicking.
|
||||
func TestExtractToolTrace_MetadataNotAnObject(t *testing.T) {
|
||||
resp := map[string]interface{}{
|
||||
"result": map[string]interface{}{
|
||||
"metadata": "not an object",
|
||||
},
|
||||
}
|
||||
body, _ := json.Marshal(resp)
|
||||
if got := extractToolTrace(body); got != nil {
|
||||
t.Errorf("expected nil for non-object metadata, got: %s", string(got))
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtractToolTrace_TraceIsEmptyArray verifies that an empty tool_trace
|
||||
// array ([]) returns nil (length 0).
|
||||
func TestExtractToolTrace_TraceIsEmptyArray(t *testing.T) {
|
||||
resp := map[string]interface{}{
|
||||
"result": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"tool_trace": []interface{}{},
|
||||
},
|
||||
},
|
||||
}
|
||||
body, _ := json.Marshal(resp)
|
||||
if got := extractToolTrace(body); got != nil {
|
||||
t.Errorf("expected nil for empty tool_trace, got: %s", string(got))
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtractToolTrace_NonJSONBody verifies that a completely non-JSON body
|
||||
// returns nil without panicking.
|
||||
func TestExtractToolTrace_NonJSONBody(t *testing.T) {
|
||||
body := []byte("this is not json at all")
|
||||
if got := extractToolTrace(body); got != nil {
|
||||
t.Errorf("expected nil for non-JSON body, got: %s", string(got))
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtractToolTrace_EmptyBody verifies that an empty body returns nil.
|
||||
func TestExtractToolTrace_EmptyBody(t *testing.T) {
|
||||
if got := extractToolTrace(nil); got != nil {
|
||||
t.Errorf("expected nil for nil body, got: %s", string(got))
|
||||
}
|
||||
if got := extractToolTrace([]byte{}); got != nil {
|
||||
t.Errorf("expected nil for empty body, got: %s", string(got))
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtractToolTrace_ResultMetadataIsNotObject verifies that when
|
||||
// metadata exists but is not a JSON object (string), nil is returned.
|
||||
func TestExtractToolTrace_MetadataIsString(t *testing.T) {
|
||||
body := []byte(`{"result":{"metadata":"oops"}}`)
|
||||
if got := extractToolTrace(body); got != nil {
|
||||
t.Errorf("expected nil for string metadata, got: %s", string(got))
|
||||
}
|
||||
}
|
||||
|
||||
// TestNilIfEmpty_Contract exercises the contract of nilIfEmpty so future
|
||||
// refactors can't silently break the call-sites in a2a_proxy_helpers.go.
|
||||
func TestNilIfEmpty_Contract(t *testing.T) {
|
||||
if r := nilIfEmpty(""); r != nil {
|
||||
t.Errorf("nilIfEmpty(\"\") = %p, want nil", r)
|
||||
}
|
||||
if r := nilIfEmpty("hello"); r == nil {
|
||||
t.Fatal("nilIfEmpty(\"hello\") returned nil, want pointer to string")
|
||||
} else if *r != "hello" {
|
||||
t.Errorf("nilIfEmpty(\"hello\") = %q, want \"hello\"", *r)
|
||||
}
|
||||
}
|
||||
|
||||
// Suppress unused import warning — setupTestDB references db.DB but this file
|
||||
// only tests pure functions, so db is only needed transitively through helpers.
|
||||
var _ = db.DB
|
||||
@@ -0,0 +1,310 @@
|
||||
package handlers
|
||||
|
||||
// plugins_atomic_tar_test.go — unit tests for tarWalk (the only non-trivial
|
||||
// function in plugins_atomic_tar.go). The file contains only pure tar-walk
|
||||
// logic with no DB or HTTP dependencies, so tests use real temp directories
|
||||
// with no mocking.
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ─── newTarWriter ─────────────────────────────────────────────────────────────
|
||||
|
||||
func TestNewTarWriter_Basic(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
tw := newTarWriter(&buf)
|
||||
if tw == nil {
|
||||
t.Fatal("newTarWriter returned nil")
|
||||
}
|
||||
// Write a header to prove the writer is functional.
|
||||
hdr := &tar.Header{
|
||||
Name: "test.txt",
|
||||
Mode: 0644,
|
||||
Size: 5,
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
t.Fatalf("WriteHeader failed: %v", err)
|
||||
}
|
||||
if _, err := tw.Write([]byte("hello")); err != nil {
|
||||
t.Fatalf("Write failed: %v", err)
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
t.Fatalf("Close failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ─── tarWalk: empty directory ─────────────────────────────────────────────────
|
||||
|
||||
func TestTarWalk_EmptyDir(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
var buf bytes.Buffer
|
||||
tw := tar.NewWriter(&buf)
|
||||
|
||||
if err := tarWalk(tmp, "prefix", tw); err != nil {
|
||||
t.Fatalf("tarWalk error: %v", err)
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
t.Fatalf("tw.Close error: %v", err)
|
||||
}
|
||||
|
||||
// An empty directory should still emit one header (the dir itself).
|
||||
rdr := tar.NewReader(&buf)
|
||||
hdr, err := rdr.Next()
|
||||
if err != nil {
|
||||
t.Fatalf("expected at least the dir header, got error: %v", err)
|
||||
}
|
||||
if !strings.HasSuffix(hdr.Name, "/") {
|
||||
t.Errorf("expected directory name ending in '/', got %q", hdr.Name)
|
||||
}
|
||||
|
||||
// No more entries.
|
||||
if _, err := rdr.Next(); err != io.EOF {
|
||||
t.Errorf("expected only one header, got more: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ─── tarWalk: single file ─────────────────────────────────────────────────────
|
||||
|
||||
func TestTarWalk_SingleFile(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(tmp, "hello.txt"), []byte("world"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
tw := tar.NewWriter(&buf)
|
||||
if err := tarWalk(tmp, "mydir", tw); err != nil {
|
||||
t.Fatalf("tarWalk error: %v", err)
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Should have 2 entries: the dir prefix, then hello.txt.
|
||||
entries := 0
|
||||
names := []string{}
|
||||
rdr := tar.NewReader(&buf)
|
||||
for {
|
||||
hdr, err := rdr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error reading tar: %v", err)
|
||||
}
|
||||
entries++
|
||||
names = append(names, hdr.Name)
|
||||
|
||||
if hdr.Name == "mydir/hello.txt" {
|
||||
if hdr.Size != 5 {
|
||||
t.Errorf("expected size 5, got %d", hdr.Size)
|
||||
}
|
||||
content := make([]byte, 5)
|
||||
if _, err := rdr.Read(content); err != nil && err != io.EOF {
|
||||
t.Fatalf("read error: %v", err)
|
||||
}
|
||||
if string(content) != "world" {
|
||||
t.Errorf("expected 'world', got %q", string(content))
|
||||
}
|
||||
}
|
||||
}
|
||||
if entries != 2 {
|
||||
t.Errorf("expected 2 entries, got %d: %v", entries, names)
|
||||
}
|
||||
}
|
||||
|
||||
// ─── tarWalk: nested directories ───────────────────────────────────────────────
|
||||
|
||||
func TestTarWalk_NestedDirs(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
subdir := filepath.Join(tmp, "a", "b", "c")
|
||||
if err := os.MkdirAll(subdir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(subdir, "deep.txt"), []byte("nested"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
tw := tar.NewWriter(&buf)
|
||||
if err := tarWalk(tmp, "root", tw); err != nil {
|
||||
t.Fatalf("tarWalk error: %v", err)
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Collect all file paths (not dirs) with content.
|
||||
files := map[string]string{}
|
||||
rdr := tar.NewReader(&buf)
|
||||
for {
|
||||
hdr, err := rdr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.HasSuffix(hdr.Name, "/") && hdr.Size > 0 {
|
||||
content := make([]byte, hdr.Size)
|
||||
rdr.Read(content)
|
||||
files[hdr.Name] = string(content)
|
||||
}
|
||||
}
|
||||
|
||||
expected := "root/a/b/c/deep.txt"
|
||||
if _, ok := files[expected]; !ok {
|
||||
t.Errorf("expected file %q in tar; got: %v", expected, files)
|
||||
} else if files[expected] != "nested" {
|
||||
t.Errorf("expected content 'nested', got %q", files[expected])
|
||||
}
|
||||
}
|
||||
|
||||
// ─── tarWalk: symlinks are skipped ────────────────────────────────────────────
|
||||
|
||||
func TestTarWalk_SymlinksSkipped(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
|
||||
// Create a real file.
|
||||
realPath := filepath.Join(tmp, "real.txt")
|
||||
if err := os.WriteFile(realPath, []byte("real content"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a symlink to it.
|
||||
linkPath := filepath.Join(tmp, "link.txt")
|
||||
if err := os.Symlink(realPath, linkPath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
tw := tar.NewWriter(&buf)
|
||||
if err := tarWalk(tmp, "prefix", tw); err != nil {
|
||||
t.Fatalf("tarWalk error: %v", err)
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Only real.txt should appear; link.txt should be absent.
|
||||
names := []string{}
|
||||
rdr := tar.NewReader(&buf)
|
||||
for {
|
||||
hdr, err := rdr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
names = append(names, hdr.Name)
|
||||
}
|
||||
|
||||
foundLink := false
|
||||
for _, n := range names {
|
||||
if strings.Contains(n, "link") {
|
||||
foundLink = true
|
||||
}
|
||||
}
|
||||
if foundLink {
|
||||
t.Errorf("symlink should be skipped; got names: %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
// ─── tarWalk: prefix trailing slash is normalized ─────────────────────────────
|
||||
|
||||
func TestTarWalk_PrefixTrailingSlashNormalized(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(tmp, "f.txt"), []byte("x"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
tw := tar.NewWriter(&buf)
|
||||
// Pass prefix WITH trailing slash — should produce same archive as without.
|
||||
if err := tarWalk(tmp, "foo/", tw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// The file should be under "foo/", not "foo//".
|
||||
rdr := tar.NewReader(&buf)
|
||||
for {
|
||||
hdr, err := rdr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.HasSuffix(hdr.Name, "/") && strings.Contains(hdr.Name, "f.txt") {
|
||||
if strings.Contains(hdr.Name, "//") {
|
||||
t.Errorf("double slash found in path %q — trailing slash not normalized", hdr.Name)
|
||||
}
|
||||
if !strings.HasPrefix(hdr.Name, "foo/") {
|
||||
t.Errorf("expected path to start with 'foo/', got %q", hdr.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── tarWalk: prefix = "." emits flat paths ───────────────────────────────────
|
||||
|
||||
func TestTarWalk_PrefixDotEmitsFlatPaths(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
subdir := filepath.Join(tmp, "sub")
|
||||
if err := os.MkdirAll(subdir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(subdir, "file.txt"), []byte("data"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
tw := tar.NewWriter(&buf)
|
||||
if err := tarWalk(tmp, ".", tw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// With prefix ".", paths should NOT start with "./" (filepath.Clean normalizes it).
|
||||
rdr := tar.NewReader(&buf)
|
||||
for {
|
||||
hdr, err := rdr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.HasSuffix(hdr.Name, "/") && strings.Contains(hdr.Name, "file.txt") {
|
||||
if strings.HasPrefix(hdr.Name, "./") {
|
||||
t.Errorf("prefix '.' should not emit './' prefix; got %q", hdr.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── tarWalk: walk error propagates ───────────────────────────────────────────
|
||||
|
||||
func TestTarWalk_NonexistentDir(t *testing.T) {
|
||||
nonexistent := filepath.Join(t.TempDir(), "does-not-exist")
|
||||
var buf bytes.Buffer
|
||||
tw := tar.NewWriter(&buf)
|
||||
|
||||
err := tarWalk(nonexistent, "x", tw)
|
||||
if err == nil {
|
||||
t.Error("expected error for nonexistent directory, got nil")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user