obm/internal/destroy/destroy_test.go
MermaidMan 33d9a2cb2e deploy walkthrough, API validation, inference client, Hetzner provider
- Interactive deploy command with 8-step walkthrough:
  framework → provider → token → SSH → server → inference → tailscale → discord
- .env file generation from walkthrough config
- DeploymentConfig struct with framework-aware defaults
- Inference API client with validation for Venice, OpenRouter, OpenAI, Anthropic
- Hetzner Cloud provider: token validation, SSH key listing
- DotEnv parser/writer with schema validation
- Destroy command with confirmation prompt
- Validation subcommand for checking existing .env files
- All tests passing, go vet clean
2026-05-22 15:29:27 +00:00

258 lines
No EOL
6.6 KiB
Go

package destroy
import (
"encoding/json"
"os"
"path/filepath"
"testing"
)
func TestListResourcesFromState(t *testing.T) {
tests := []struct {
name string
content string
want []Resource
wantErr bool
}{
{
name: "empty state",
content: `{}`,
want: []Resource{},
wantErr: false,
},
{
name: "state with resources",
content: `{"resources":[{"address":"hcloud_server.main","type":"hcloud_server","name":"main"},{"address":"hcloud_volume.data","type":"hcloud_volume","name":"data"}]}`,
want: []Resource{
{Address: "hcloud_server.main", Type: "hcloud_server", Name: "main"},
{Address: "hcloud_volume.data", Type: "hcloud_volume", Name: "data"},
},
wantErr: false,
},
{
name: "state with module resources",
content: `{"resources":[{"address":"hcloud_server.main","type":"hcloud_server","name":"main","module":"module.agent"},{"address":"null_resource.provisioner","type":"null_resource","name":"provisioner"}]}`,
want: []Resource{
{Address: "module.agent.hcloud_server.main", Type: "hcloud_server", Name: "main"},
{Address: "null_resource.provisioner", Type: "null_resource", Name: "provisioner"},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create temp file
tmpDir := t.TempDir()
statePath := filepath.Join(tmpDir, "terraform.tfstate")
if err := os.WriteFile(statePath, []byte(tt.content), 0644); err != nil {
t.Fatalf("failed to write state file: %v", err)
}
got, err := listResourcesFromState(statePath)
if (err != nil) != tt.wantErr {
t.Errorf("listResourcesFromState() error = %v, wantErr %v", err, tt.wantErr)
return
}
if len(got) != len(tt.want) {
t.Errorf("listResourcesFromState() got %d resources, want %d", len(got), len(tt.want))
return
}
for i, r := range got {
if r.Address != tt.want[i].Address {
t.Errorf("resource[%d].Address = %s, want %s", i, r.Address, tt.want[i].Address)
}
if r.Type != tt.want[i].Type {
t.Errorf("resource[%d].Type = %s, want %s", i, r.Type, tt.want[i].Type)
}
if r.Name != tt.want[i].Name {
t.Errorf("resource[%d].Name = %s, want %s", i, r.Name, tt.want[i].Name)
}
}
})
}
}
func TestListResourcesFromStateNonExistent(t *testing.T) {
_, err := listResourcesFromState("/nonexistent/path/state")
if err == nil {
t.Error("expected error for non-existent file")
}
}
func TestLoadEnvFile(t *testing.T) {
tests := []struct {
name string
content string
want []string
wantErr bool
}{
{
name: "simple key-value",
content: "KEY=value\nOTHER=123",
want: []string{"KEY=value", "OTHER=123"},
wantErr: false,
},
{
name: "quoted values",
content: `KEY="quoted value"` + "\n" + `OTHER='single quoted'`,
want: []string{"KEY=quoted value", "OTHER=single quoted"},
wantErr: false,
},
{
name: "comments and blank lines",
content: "# comment\n\nKEY=value\n# another comment\n",
want: []string{"KEY=value"},
wantErr: false,
},
{
name: "empty file",
content: "",
want: []string{},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create temp file
tmpFile := filepath.Join(t.TempDir(), ".env")
if err := os.WriteFile(tmpFile, []byte(tt.content), 0644); err != nil {
t.Fatalf("failed to write env file: %v", err)
}
got, err := loadEnvFile(tmpFile)
if (err != nil) != tt.wantErr {
t.Errorf("loadEnvFile() error = %v, wantErr %v", err, tt.wantErr)
return
}
if len(got) != len(tt.want) {
t.Errorf("loadEnvFile() got %d entries, want %d", len(got), len(tt.want))
return
}
for i, v := range got {
if v != tt.want[i] {
t.Errorf("env[%d] = %s, want %s", i, v, tt.want[i])
}
}
})
}
}
func TestFileExists(t *testing.T) {
tmpDir := t.TempDir()
// Test existing file
existingFile := filepath.Join(tmpDir, "exists")
if err := os.WriteFile(existingFile, []byte("test"), 0644); err != nil {
t.Fatal(err)
}
if !fileExists(existingFile) {
t.Error("fileExists() returned false for existing file")
}
// Test non-existent file
if fileExists(filepath.Join(tmpDir, "nonexistent")) {
t.Error("fileExists() returned true for non-existent file")
}
// Test directory (should return false)
if fileExists(tmpDir) {
t.Error("fileExists() returned true for directory")
}
}
func TestDirExists(t *testing.T) {
tmpDir := t.TempDir()
// Test existing directory
if !dirExists(tmpDir) {
t.Error("dirExists() returned false for existing directory")
}
// Test non-existent directory
if dirExists(filepath.Join(tmpDir, "nonexistent")) {
t.Error("dirExists() returned true for non-existent directory")
}
// Test file (should return false)
existingFile := filepath.Join(tmpDir, "file")
if err := os.WriteFile(existingFile, []byte("test"), 0644); err != nil {
t.Fatal(err)
}
if dirExists(existingFile) {
t.Error("dirExists() returned true for file")
}
}
func TestCleanupStateFiles(t *testing.T) {
tmpDir := t.TempDir()
// Create state files
stateFiles := []string{
"terraform.tfstate",
"terraform.tfstate.backup",
".terraform.lock.hcl",
}
for _, f := range stateFiles {
if err := os.WriteFile(filepath.Join(tmpDir, f), []byte("{}"), 0644); err != nil {
t.Fatal(err)
}
}
// Create .terraform directory
tfDir := filepath.Join(tmpDir, ".terraform")
if err := os.MkdirAll(tfDir, 0755); err != nil {
t.Fatal(err)
}
// Run cleanup
cleanupStateFiles(tmpDir)
// Verify files are deleted
for _, f := range stateFiles {
if fileExists(filepath.Join(tmpDir, f)) {
t.Errorf("state file %s was not deleted", f)
}
}
if dirExists(tfDir) {
t.Error(".terraform directory was not deleted")
}
}
func TestResourceJSONMarshal(t *testing.T) {
// Verify Resource struct can be marshaled/unmarshaled if needed
res := Resource{
Address: "hcloud_server.main",
Type: "hcloud_server",
Name: "main",
}
data, err := json.Marshal(res)
if err != nil {
t.Fatalf("failed to marshal Resource: %v", err)
}
var got Resource
if err := json.Unmarshal(data, &got); err != nil {
t.Fatalf("failed to unmarshal Resource: %v", err)
}
if got.Address != res.Address || got.Type != res.Type || got.Name != res.Name {
t.Errorf("marshal/unmarshal roundtrip failed: got %+v, want %+v", got, res)
}
}
func TestOptionsDefaults(t *testing.T) {
// Test that Options struct can be created with defaults
opts := &Options{}
if opts.AutoApprove != false {
t.Error("default AutoApprove should be false")
}
if opts.WorkDir != "" {
t.Error("default WorkDir should be empty")
}
if opts.KeepState != false {
t.Error("default KeepState should be false")
}
}