banger/internal/guest/ssh_test.go
Thales Maciel 2ebc6f99c6
Add repo-backed vm run command
Create a CLI-only banger vm run [path] flow that resolves the enclosing git repository, creates a VM, imports a guest checkout, and launches opencode attach automatically from the host.

Build the guest checkout by bundling git history plus the resolved base and head commits, cloning that bundle in the guest, and overlaying tracked plus untracked non-ignored files over SSH so local working-tree changes carry over. Support guest-only branch creation with --branch and --from, reject bare repos and submodules, and add selective tar helpers plus CLI seams to keep the workflow testable.

Validate with go test ./..., make build, banger vm run --help, and the expected --from requires --branch error path.
2026-03-21 23:34:20 -03:00

142 lines
3.7 KiB
Go

package guest
import (
"archive/tar"
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"io"
"os"
"path/filepath"
"testing"
"golang.org/x/crypto/ssh"
)
func TestWriteTarArchiveKeepsTopLevelDirectory(t *testing.T) {
t.Parallel()
sourceDir := filepath.Join(t.TempDir(), "6.8.0-test")
if err := os.MkdirAll(filepath.Join(sourceDir, "kernel"), 0o755); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
if err := os.WriteFile(filepath.Join(sourceDir, "modules.dep"), []byte("deps"), 0o644); err != nil {
t.Fatalf("WriteFile modules.dep: %v", err)
}
if err := os.WriteFile(filepath.Join(sourceDir, "kernel", "module.ko"), []byte("ko"), 0o644); err != nil {
t.Fatalf("WriteFile module.ko: %v", err)
}
var buf bytes.Buffer
if err := writeTarArchive(&buf, sourceDir); err != nil {
t.Fatalf("writeTarArchive: %v", err)
}
tr := tar.NewReader(bytes.NewReader(buf.Bytes()))
var names []string
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("tar.Next: %v", err)
}
names = append(names, header.Name)
}
want := map[string]struct{}{
"6.8.0-test": {},
"6.8.0-test/modules.dep": {},
"6.8.0-test/kernel": {},
"6.8.0-test/kernel/module.ko": {},
}
if len(names) != len(want) {
t.Fatalf("archive names = %v, want %d entries", names, len(want))
}
for _, name := range names {
if _, ok := want[name]; !ok {
t.Fatalf("unexpected archive entry %q in %v", name, names)
}
}
}
func TestAuthorizedPublicKey(t *testing.T) {
t.Parallel()
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
t.Fatalf("GenerateKey: %v", err)
}
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
})
keyPath := filepath.Join(t.TempDir(), "id_rsa")
if err := os.WriteFile(keyPath, privateKeyPEM, 0o600); err != nil {
t.Fatalf("WriteFile: %v", err)
}
publicKey, err := AuthorizedPublicKey(keyPath)
if err != nil {
t.Fatalf("AuthorizedPublicKey: %v", err)
}
parsed, _, _, _, err := ssh.ParseAuthorizedKey(publicKey)
if err != nil {
t.Fatalf("ParseAuthorizedKey: %v", err)
}
if parsed.Type() != ssh.KeyAlgoRSA {
t.Fatalf("key type = %q, want %q", parsed.Type(), ssh.KeyAlgoRSA)
}
}
func TestWriteTarEntriesArchiveIncludesOnlySelectedPaths(t *testing.T) {
t.Parallel()
sourceDir := filepath.Join(t.TempDir(), "repo")
if err := os.MkdirAll(filepath.Join(sourceDir, "nested"), 0o755); err != nil {
t.Fatalf("MkdirAll(nested): %v", err)
}
if err := os.WriteFile(filepath.Join(sourceDir, "tracked.txt"), []byte("tracked"), 0o644); err != nil {
t.Fatalf("WriteFile(tracked.txt): %v", err)
}
if err := os.WriteFile(filepath.Join(sourceDir, "nested", "keep.txt"), []byte("keep"), 0o644); err != nil {
t.Fatalf("WriteFile(keep.txt): %v", err)
}
if err := os.WriteFile(filepath.Join(sourceDir, "nested", "skip.txt"), []byte("skip"), 0o644); err != nil {
t.Fatalf("WriteFile(skip.txt): %v", err)
}
var buf bytes.Buffer
if err := writeTarEntriesArchive(&buf, sourceDir, []string{"tracked.txt", "nested/keep.txt"}); err != nil {
t.Fatalf("writeTarEntriesArchive: %v", err)
}
tr := tar.NewReader(bytes.NewReader(buf.Bytes()))
var names []string
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("tar.Next: %v", err)
}
names = append(names, header.Name)
}
want := map[string]struct{}{
"repo/tracked.txt": {},
"repo/nested/keep.txt": {},
}
if len(names) != len(want) {
t.Fatalf("archive names = %v, want %d entries", names, len(want))
}
for _, name := range names {
if _, ok := want[name]; !ok {
t.Fatalf("unexpected archive entry %q in %v", name, names)
}
}
}