E1: split monolithic files — cmd/nomos (main.go → server.go + mcp.go + workers.go),
internal/mcp/tools.go → entity_tools/ops_tools/knowledge_tools/analysis_tools,
internal/httpapi/impl.go → domain files (entities, events, signals, ontology,
fleet_health, client_context, client_lifecycle, entity_mutations, query_audit).
E2: migrate raw pool.Exec queries to sqlc (entities/relationships queries + generated).
E3: unify SSH — consolidate crypto/ssh dial into actuator/client.go (+client_test).
E4/E5: add tests — db/lifecycle, checkdefaults/build, ontology/preconditions, policy/risk.
89 lines
2.3 KiB
Go
89 lines
2.3 KiB
Go
package actuator
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"encoding/pem"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
func TestLoadSignerRejectsBadInput(t *testing.T) {
|
|
if _, err := LoadSignerFromBytes([]byte("not a private key")); err == nil {
|
|
t.Error("LoadSignerFromBytes should reject a non-key input")
|
|
}
|
|
if _, err := LoadSigner("/nonexistent/key"); err == nil {
|
|
t.Error("LoadSigner should fail on a missing file")
|
|
}
|
|
}
|
|
|
|
func TestLoadSignerRoundTrip(t *testing.T) {
|
|
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatalf("generate key: %v", err)
|
|
}
|
|
block, err := ssh.MarshalPrivateKey(priv, "")
|
|
if err != nil {
|
|
t.Fatalf("marshal private key: %v", err)
|
|
}
|
|
pemBytes := pem.EncodeToMemory(block)
|
|
|
|
signer, err := LoadSignerFromBytes(pemBytes)
|
|
if err != nil {
|
|
t.Fatalf("LoadSignerFromBytes on a valid key: %v", err)
|
|
}
|
|
if signer == nil {
|
|
t.Fatal("signer is nil")
|
|
}
|
|
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "id_ed25519")
|
|
if err := os.WriteFile(path, pemBytes, 0o600); err != nil {
|
|
t.Fatalf("write key file: %v", err)
|
|
}
|
|
fromFile, err := LoadSigner(path)
|
|
if err != nil {
|
|
t.Fatalf("LoadSigner(%s): %v", path, err)
|
|
}
|
|
if !bytes.Equal(fromFile.PublicKey().Marshal(), signer.PublicKey().Marshal()) {
|
|
t.Error("file and in-memory signers resolved to different public keys")
|
|
}
|
|
}
|
|
|
|
// Dial needs a real SSH server to run a command, but its option normalization
|
|
// is verifiable without one: a zero Port must default to 22 (so the dial error
|
|
// references host:22, not host:0), and a closed port yields a dial error rather
|
|
// than panicking.
|
|
func TestDialDefaultsPort(t *testing.T) {
|
|
_, err := Dial(context.Background(), DialOptions{Host: "127.0.0.1", Signer: mustSigner(t)})
|
|
if err == nil {
|
|
t.Fatal("Dial to a closed port should fail")
|
|
}
|
|
if !strings.Contains(err.Error(), "127.0.0.1:22") {
|
|
t.Errorf("Dial error = %q, want it to reference 127.0.0.1:22", err)
|
|
}
|
|
}
|
|
|
|
func mustSigner(t *testing.T) ssh.Signer {
|
|
t.Helper()
|
|
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatalf("generate key: %v", err)
|
|
}
|
|
block, err := ssh.MarshalPrivateKey(priv, "")
|
|
if err != nil {
|
|
t.Fatalf("marshal key: %v", err)
|
|
}
|
|
s, err := LoadSignerFromBytes(pem.EncodeToMemory(block))
|
|
if err != nil {
|
|
t.Fatalf("parse key: %v", err)
|
|
}
|
|
return s
|
|
}
|