// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "context" "encoding/json" "errors" "os" "path/filepath" "testing" "time" "git.stormux.org/storm/cthulhu/internal/tentacle" "git.stormux.org/storm/cthulhu/internal/tentaclewire" sdk "git.stormux.org/storm/cthulhu/sdk/go/tentacle" ) func TestRemoteManagementPeer(t *testing.T) { if len(os.Args) < 2 || os.Args[len(os.Args)-1] != "remote-management-peer" { return } client := sdk.NewSessionClient() command := sdk.Command{ID: "open", Title: "Open", Handle: func(ctx context.Context, _ json.RawMessage) (any, error) { var event sdk.SessionEvent select { case event = <-client.Events(): case <-ctx.Done(): return nil, ctx.Err() } var lease sdk.RemoteLease err := client.Call(ctx, "remote.open", sdk.RemoteOpen{Action: event.Action, Epoch: event.Epoch, Role: event.Role, Grants: event.Grants}, &lease) return lease, err }} if sdk.ServeSession(os.Stdin, os.Stdout, []sdk.Command{command}, nil, client) != nil { os.Exit(1) } os.Exit(0) } func TestRemoteLeaseRevokedBeforeFailedUpdatePreparation(t *testing.T) { executable, err := os.Executable() if err != nil { t.Fatal(err) } manager, err := tentacle.NewManager(tentacle.DefaultLimits(), nil) if err != nil { t.Fatal(err) } defer func() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := manager.Shutdown(ctx); err != nil { t.Error(err) } }() repository := tentacle.Repository{URL: "https://example.org/tentacles", ID: "example.source", Tentacles: []tentacle.Manifest{{ID: "probe", Version: "2.0"}}} id := tentacle.PackageID(repository, "probe") session, err := manager.Start(t.Context(), tentacle.Spec{ID: id, Program: []string{executable, "-test.run=^TestRemoteManagementPeer$", "--", "remote-management-peer"}, SessionEvents: true, RemoteGrants: []string{"output-observe"}}) if err != nil { t.Fatal(err) } select { case <-session.Ready(): case <-time.After(5 * time.Second): t.Fatal("peer not ready") } if err := session.Activate(); err != nil { t.Fatal(err) } if _, err := session.AuthorizeRemote("controlled", []string{"output-observe"}); err != nil { t.Fatal(err) } pending, err := session.Invoke(t.Context(), "open", nil) if err != nil { t.Fatal(err) } var lease tentaclewire.RemoteLease select { case result := <-pending: if result.Err != nil || json.Unmarshal(result.Value, &lease) != nil { t.Fatal("open failed", result.Err) } case <-time.After(5 * time.Second): t.Fatal("open timed out") } delivery, err := session.RemoteContext(lease, "output-observe") if err != nil { t.Fatal(err) } owner := &tentacleRuntime{registry: &tentacle.Registry{Path: filepath.Join(t.TempDir(), "registry.toml")}, lifecycleContext: t.Context(), sessions: []*tentacle.Session{session}, snapshot: tentacle.Snapshot{Repositories: []tentacle.Repository{repository}, Entries: []tentacle.EntryStatus{{Entry: tentacle.Entry{ID: id, Version: "1.0", Enabled: true}}}}} failure := errors.New("fixture preparation failure") called := false owner.preparePackage = func(context.Context, string, tentacle.Repository, string) (*tentacle.PreparedPackage, error) { called = true if delivery.Err() == nil { t.Error("preparation began with live remote access") } return nil, failure } if err := owner.Manage(t.Context(), tentacle.ManagementRequest{Action: "upgrade", URL: repository.URL, ID: "probe"}); !errors.Is(err, failure) { t.Fatal("wrong update result", err) } if !called || delivery.Err() == nil { t.Fatal("failed update retained access") } if _, err := session.RemoteContext(lease, "output-observe"); err == nil { t.Fatal("failed update restored consent") } if state, err := session.Status(); state != tentacle.Active || err != nil { t.Fatal("failed preparation unnecessarily stopped ordinary commands", state, err) } }