Files

112 lines
4.6 KiB
Go

// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package tentacle
import (
"context"
"encoding/json"
"errors"
"git.stormux.org/storm/cthulhu/internal/tentaclewire"
sdk "git.stormux.org/storm/cthulhu/sdk/go/tentacle"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestReaderSDKPeer(t *testing.T) {
if os.Getenv("CTHULHU_PRIVATE_READER_PEER") != "1" {
return
}
extension := sdk.Extensions{Pages: []sdk.PreferencePage{{ID: "probe", Title: "Reader probe", Command: "echo", Elements: []sdk.Element{{ID: "text", Label: "Example text", Kind: sdk.Entry}}, Values: map[string]sdk.Value{"text": {Text: "Initial"}}}}, QuickSettings: []sdk.QuickSetting{{ID: "probe", Label: "Probe choice", Command: "echo", Selected: "default", Options: []sdk.ListItem{{ID: "default", Label: "Default"}}}}}
echo := func(ctx context.Context, args json.RawMessage) (any, error) { return args, nil }
if err := sdk.ServeWithExtensions(os.Stdin, os.Stdout, []sdk.Command{{ID: "echo", Title: "Reader probe", Presentation: true, Interaction: true, Reader: true, Handle: echo}, {ID: "ordinary", Title: "Ordinary probe", Handle: echo}}, extension); err != nil {
os.Exit(1)
}
os.Exit(0)
}
func TestReaderSDKGrantsCopiesAndArgumentBounds(t *testing.T) {
t.Setenv("CTHULHU_PRIVATE_READER_PEER", "1")
for _, language := range []string{"go", "python"} {
for _, grant := range []bool{false, true} {
t.Run(language+"/"+map[bool]string{false: "denied", true: "granted"}[grant], func(t *testing.T) {
var program []string
if language == "go" {
executable, _ := os.Executable()
program = []string{executable, "-test.run=^TestReaderSDKPeer$"}
} else {
python, err := exec.LookPath("python3")
if err != nil {
t.Skip(err)
}
peer, err := filepath.Abs("testdata/sdk_reader_peer.py")
if err != nil {
t.Fatal(err)
}
program = []string{python, "-B", peer}
}
manager := test_manager(t, DefaultLimits(), nil)
session, err := manager.Start(t.Context(), Spec{ID: "probe", Program: program, Presentation: true, Interaction: true, ReaderSettings: grant})
if err != nil {
t.Fatal(err)
}
wait_signal(t, session.Ready())
state, reason := session.Status()
if !grant {
if state == Ready {
t.Fatal("reader API accepted without grant")
}
return
}
if state != Ready {
t.Fatal(state, reason)
}
extensions := session.Extensions()
if len(extensions.Pages) != 1 || len(extensions.QuickSettings) != 1 {
t.Fatal(extensions)
}
extensions.Pages[0].Values["text"] = sdk.Value{Text: "Changed"}
extensions.Pages[0].Elements[0].Label = "Changed"
if session.Extensions().Pages[0].Values["text"].Text != "Initial" || session.Extensions().Pages[0].Elements[0].Label != "Example text" {
t.Fatal("registration shared mutable state")
}
if err := session.Activate(); err != nil {
t.Fatal(err)
}
argument := json.RawMessage(`{"text":"` + strings.Repeat("x", 24*1024) + `"}`)
replies, err := session.Invoke(t.Context(), "echo", argument)
if err != nil {
t.Fatal(err)
}
result := wait_result(t, replies)
if result.Err != nil || string(result.Value) != string(argument) {
t.Fatal("reader continuation did not round trip", result.Err)
}
if _, err := session.Invoke(t.Context(), "ordinary", argument); !errors.Is(err, ErrLimit) {
t.Fatal("ordinary command exceeded its original argument limit", err)
}
if _, err := session.Invoke(t.Context(), "echo", json.RawMessage(`"`+strings.Repeat("x", MaxReaderArgumentBytes)+`"`)); !errors.Is(err, ErrLimit) {
t.Fatal("reader limit not enforced", err)
}
})
}
}
}
func TestReaderRegistrationRequiresDeclaredReaderCallbacks(t *testing.T) {
extension := tentaclewire.Extensions{QuickSettings: []tentaclewire.QuickSetting{{ID: "probe", Label: "Probe", Command: "apply", Selected: "default", Options: []sdk.ListItem{{ID: "default", Label: "Default"}}}}}
registration := Registration{Protocol: Version{Major: 1}, Session: "session", Commands: []Command{{ID: "apply", Title: "Apply", Presentation: true, Interaction: true, Reader: true}}, Extensions: &extension}
if err := validate_registration(registration, "session"); err != nil {
t.Fatal(err)
}
registration.Commands[0].Reader = false
if validate_registration(registration, "session") == nil {
t.Fatal("accepted an unprivileged extension callback")
}
registration.Commands[0].Reader = true
registration.Commands[0].Settings = true
if validate_registration(registration, "session") == nil {
t.Fatal("accepted a settings-form handler as a page callback")
}
}