// 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") } }