Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions src/internal/cloud/clouds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cloud
import (
"os"
"path/filepath"
"strings"
"testing"
)

Expand Down Expand Up @@ -79,3 +80,186 @@ func TestListCloudNames_NoFile(t *testing.T) {
t.Error("expected error when no clouds.yaml exists")
}
}

func TestCloudsYamlPaths_Order(t *testing.T) {
t.Setenv("OS_CLIENT_CONFIG_FILE", "/custom/path/clouds.yaml")
paths := CloudsYamlPaths()

// First path should always be relative clouds.yaml
if paths[0] != "clouds.yaml" {
t.Errorf("first path should be 'clouds.yaml', got %s", paths[0])
}

// Second path should be OS_CLIENT_CONFIG_FILE (when set)
if paths[1] != "/custom/path/clouds.yaml" {
t.Errorf("second path should be OS_CLIENT_CONFIG_FILE, got %s", paths[1])
}

// Last path should be the system-wide path
if paths[len(paths)-1] != "/etc/openstack/clouds.yaml" {
t.Errorf("last path should be /etc/openstack/clouds.yaml, got %s", paths[len(paths)-1])
}
}

func TestCloudsYamlPaths_WithoutEnv(t *testing.T) {
// Ensure OS_CLIENT_CONFIG_FILE is not set
t.Setenv("OS_CLIENT_CONFIG_FILE", "")
paths := CloudsYamlPaths()

// Should have 3 paths: relative, home, system
if len(paths) != 3 {
t.Errorf("expected 3 paths without env, got %d: %v", len(paths), paths)
}

if paths[0] != "clouds.yaml" {
t.Errorf("first path should be 'clouds.yaml', got %s", paths[0])
}
}

func TestCloudsYamlPaths_WithEnv(t *testing.T) {
t.Setenv("OS_CLIENT_CONFIG_FILE", "/my/custom/clouds.yaml")
paths := CloudsYamlPaths()

// Should have 4 paths: relative, env, home, system
if len(paths) != 4 {
t.Errorf("expected 4 paths with env, got %d: %v", len(paths), paths)
}

if paths[1] != "/my/custom/clouds.yaml" {
t.Errorf("second path should be env path, got %s", paths[1])
}
}

func TestListCloudNames_InvalidYAML(t *testing.T) {
dir := t.TempDir()
content := `clouds:
bad:
[invalid yaml {{{
`
path := filepath.Join(dir, "clouds.yaml")
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
t.Fatal(err)
}

t.Setenv("OS_CLIENT_CONFIG_FILE", path)

_, err := ListCloudNames()
Comment on lines +133 to +146
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These ListCloudNames tests can become flaky if the process working directory contains a real clouds.yaml (e.g., a developer has one in the repo root). ListCloudNames() searches clouds.yaml in the current directory before OS_CLIENT_CONFIG_FILE, so the test may read the wrong file. Consider os.Chdir(t.TempDir()) (and defer restore) like TestListCloudNames_NoFile does to isolate the current-directory lookup for this test (and similar ones below).

Copilot uses AI. Check for mistakes.
if err == nil {
t.Error("expected error for invalid YAML")
}
if !strings.Contains(err.Error(), "parsing") {
t.Errorf("error should mention parsing, got: %v", err)
}
}

func TestListCloudNames_NoCloudsKey(t *testing.T) {
dir := t.TempDir()
content := `not_clouds:
foo: bar
`
path := filepath.Join(dir, "clouds.yaml")
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
t.Fatal(err)
}

t.Setenv("OS_CLIENT_CONFIG_FILE", path)

names, err := ListCloudNames()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Missing 'clouds' key means empty map, so 0 names
if len(names) != 0 {
t.Errorf("expected 0 clouds when 'clouds' key is missing, got %d", len(names))
}
}

func TestListCloudNames_EmptyFile(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "clouds.yaml")
if err := os.WriteFile(path, []byte(""), 0644); err != nil {
t.Fatal(err)
}

t.Setenv("OS_CLIENT_CONFIG_FILE", path)

names, err := ListCloudNames()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(names) != 0 {
t.Errorf("expected 0 clouds for empty file, got %d", len(names))
}
}

func TestListCloudNames_Precedence(t *testing.T) {
// When OS_CLIENT_CONFIG_FILE points to a valid file, it should be found
// before the home path
dir := t.TempDir()
content := `clouds:
env_cloud:
auth:
auth_url: https://env.example.com:5000
`
path := filepath.Join(dir, "clouds.yaml")
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
t.Fatal(err)
}

t.Setenv("OS_CLIENT_CONFIG_FILE", path)
t.Setenv("HOME", dir) // ensure home path doesn't interfere

names, err := ListCloudNames()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

if len(names) != 1 || names[0] != "env_cloud" {
t.Errorf("expected [env_cloud], got %v", names)
}
}

func TestListCloudNames_DetailedCloudsYaml(t *testing.T) {
dir := t.TempDir()
content := `clouds:
production:
auth:
auth_url: https://keystone.prod.example.com:5000
username: admin
password: secret
project_name: ops
user_domain_name: Default
project_domain_name: Default
region_name: RegionOne
interface: public
development:
auth:
auth_url: https://keystone.dev.example.com:5000/v3
username: developer
project_name: dev-team
region_name: RegionOne
identity_api_version: "3"
`
path := filepath.Join(dir, "clouds.yaml")
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
t.Fatal(err)
}

t.Setenv("OS_CLIENT_CONFIG_FILE", path)

names, err := ListCloudNames()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

if len(names) != 2 {
t.Fatalf("expected 2 clouds, got %d", len(names))
}

expected := []string{"development", "production"}
for i, name := range names {
if name != expected[i] {
t.Errorf("expected %s at index %d, got %s", expected[i], i, name)
}
}
}
Loading
Loading