diff --git a/.gitignore b/.gitignore index 7f41dad..13df704 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ CLAUDE.md # Wild Cloud **/config/secrets.env **/config/config.env + +# Test directory - ignore temporary files +test/tmp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b7efcb4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "test/bats"] + path = test/bats + url = https://github.com/bats-core/bats-core.git +[submodule "test/test_helper/bats-support"] + path = test/test_helper/bats-support + url = https://github.com/bats-core/bats-support.git +[submodule "test/test_helper/bats-assert"] + path = test/test_helper/bats-assert + url = https://github.com/bats-core/bats-assert.git diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..28a5ae6 --- /dev/null +++ b/test/README.md @@ -0,0 +1,104 @@ +# Test Directory + +This directory is used for testing wild-cloud functionality using the Bats testing framework. + +## Contents +- `test_helper.bash` - Shared Bats test setup and utilities +- `test_helper/` - Bats framework extensions (bats-support, bats-assert) +- `bats/` - Bats core framework (git submodule) +- `fixtures/` - Test data and sample configuration files +- `*.bats` - Bats test files for different components +- `run_bats_tests.sh` - Runs the complete Bats test suite +- `tmp/` - Temporary test projects (auto-created/cleaned) + +## Test Files + +### `test_common_functions.bats` +Tests the core functions in `wild-common.sh`: +- `find_wc_home()` - Project root detection +- `init_wild_env()` - Environment setup +- `check_wild_directory()` - Project validation +- Print functions and utilities + +### `test_project_detection.bats` +Tests project detection and script execution: +- Script execution from various directory levels +- Environment variable setup from different paths +- Proper failure outside project directories + +### `test_config_functions.bats` +Tests configuration and secret access: +- `get_current_config()` function +- `get_current_secret()` function +- Configuration access from subdirectories +- Fixture data usage + +## Running Tests + +```bash +# Initialize git submodules (first time only) +git submodule update --init --recursive + +# Run all Bats tests +./run_bats_tests.sh + +# Run individual test files +./bats/bin/bats test_common_functions.bats +./bats/bin/bats test_project_detection.bats +./bats/bin/bats test_config_functions.bats + +# Test from subdirectory (should work) +cd deep/nested/path +../../../bin/wild-cluster-node-up --help +``` + +## Fixtures + +The `fixtures/` directory contains: +- `sample-config.yaml` - Complete test configuration +- `sample-secrets.yaml` - Test secrets file + +## Adding New Tests + +1. Create `test_.bats` following the Bats pattern: + ```bash + #!/usr/bin/env bats + + load 'test_helper' + + setup() { + setup_test_project "feature-test" + } + + teardown() { + teardown_test_project "feature-test" + } + + @test "feature description" { + # Your test here using Bats assertions + run some_command + assert_success + assert_output "expected output" + } + ``` + +2. Add test data to `fixtures/` if needed + +3. The Bats runner will automatically discover and run new tests + +## Common Test Functions + +From `test_helper.bash`: +- `setup_test_project "name"` - Creates test project in `tmp/` +- `teardown_test_project "name"` - Removes test project +- `create_test_project "name" [with-config]` - Creates additional test projects +- `remove_test_project "name"` - Removes additional test projects + +## Bats Assertions + +Available through bats-assert: +- `assert_success` / `assert_failure` - Check command exit status +- `assert_output "text"` - Check exact output +- `assert_output --partial "text"` - Check output contains text +- `assert_equal "$actual" "$expected"` - Check equality +- `assert [ condition ]` - General assertions \ No newline at end of file diff --git a/test/bats b/test/bats new file mode 160000 index 0000000..855844b --- /dev/null +++ b/test/bats @@ -0,0 +1 @@ +Subproject commit 855844b8344e67d60dc0f43fa39817ed7787f141 diff --git a/test/fixtures/sample-config.yaml b/test/fixtures/sample-config.yaml new file mode 100644 index 0000000..7aacfd0 --- /dev/null +++ b/test/fixtures/sample-config.yaml @@ -0,0 +1,72 @@ +wildcloud: + root: /test/path/wild-cloud +operator: + email: test@example.com +cloud: + domain: test.example.com + internalDomain: internal.test.example.com + dockerRegistryHost: docker-registry.internal.test.example.com + tz: America/New_York + router: + dynamicDns: test.ddns.com + ip: 192.168.100.1 + nfs: + host: test-nfs + mediaPath: /data/media + storageCapacity: 100Gi + dns: + ip: 192.168.100.50 + externalResolver: 8.8.8.8 + dhcpRange: 192.168.100.100,192.168.100.200 + dnsmasq: + interface: eth0 + username: testuser +cluster: + name: test-cluster + ipAddressPool: 192.168.100.240-192.168.100.249 + loadBalancerIp: 192.168.100.240 + kubernetes: + config: /home/testuser/.kube/config + context: default + dashboard: + adminUsername: admin + certManager: + namespace: cert-manager + cloudflare: + domain: example.com + ownerId: test-cluster-owner + externalDns: + ownerId: test-cluster-owner + dockerRegistry: + storage: 10Gi + nodes: + talos: + version: v1.10.4 + schematicId: test123456789abcdef + control: + vip: 192.168.100.200 + active: + 192.168.100.201: + maintenanceIp: 192.168.100.131 + interface: eth0 + disk: /dev/sda + control: "true" + 192.168.100.202: + interface: eth0 + disk: /dev/nvme0n1 + control: "true" + 192.168.100.210: + interface: eth0 + disk: /dev/sda + control: "false" +apps: + postgres: + database: postgres + user: postgres + storage: 10Gi + image: pgvector/pgvector:pg15 + timezone: America/New_York + redis: + image: redis:alpine + timezone: UTC + port: 6379 \ No newline at end of file diff --git a/test/fixtures/sample-secrets.yaml b/test/fixtures/sample-secrets.yaml new file mode 100644 index 0000000..9d4ebd3 --- /dev/null +++ b/test/fixtures/sample-secrets.yaml @@ -0,0 +1,14 @@ +operator: + cloudflareApiToken: test_api_token_123456789 +cluster: + dashboard: + adminPassword: test_admin_password_123 + certManager: + cloudflare: + apiToken: test_cf_token_456789 +apps: + postgres: + password: test_postgres_password_789 + databases: + immich: + password: test_immich_db_password_321 \ No newline at end of file diff --git a/test/run_bats_tests.sh b/test/run_bats_tests.sh new file mode 100755 index 0000000..1c4daac --- /dev/null +++ b/test/run_bats_tests.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# run_bats_tests.sh +# Run all Bats tests in the test directory + +set -e + +# Get the directory where this script is located +TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Check if bats is available +if [ ! -f "$TEST_DIR/bats/bin/bats" ]; then + echo "Error: Bats not found. Make sure git submodules are initialized:" + echo " git submodule update --init --recursive" + exit 1 +fi + +echo "Running Wild Cloud Bats Test Suite..." +echo "======================================" + +# Run all .bats files +"$TEST_DIR/bats/bin/bats" "$TEST_DIR"/*.bats + +echo "" +echo "All tests completed!" \ No newline at end of file diff --git a/test/test_common_functions.bats b/test/test_common_functions.bats new file mode 100644 index 0000000..83d1db7 --- /dev/null +++ b/test/test_common_functions.bats @@ -0,0 +1,86 @@ +#!/usr/bin/env bats + +# test_common_functions.bats +# Tests for the wild-common.sh library functions + +load 'test_helper' + +setup() { + setup_test_project "common-test" + cd "$TEST_PROJECT_DIR" +} + +teardown() { + teardown_test_project "common-test" +} + +@test "find_wc_home from project root" { + cd "$TEST_PROJECT_DIR" + WC_HOME_RESULT=$(find_wc_home) + assert_equal "$WC_HOME_RESULT" "$TEST_PROJECT_DIR" +} + +@test "find_wc_home from nested subdirectory" { + mkdir -p "$TEST_PROJECT_DIR/deep/nested/path" + cd "$TEST_PROJECT_DIR/deep/nested/path" + WC_HOME_RESULT=$(find_wc_home) + assert_equal "$WC_HOME_RESULT" "$TEST_PROJECT_DIR" +} + +@test "find_wc_home when no project found" { + cd /tmp + run find_wc_home + assert_failure +} + +@test "init_wild_env sets WC_HOME correctly" { + mkdir -p "$TEST_PROJECT_DIR/deep/nested" + cd "$TEST_PROJECT_DIR/deep/nested" + unset WC_HOME WC_ROOT + init_wild_env + assert_equal "$WC_HOME" "$TEST_PROJECT_DIR" +} + +@test "init_wild_env sets WC_ROOT correctly" { + cd "$TEST_PROJECT_DIR" + unset WC_HOME WC_ROOT + init_wild_env + # WC_ROOT is set (value depends on test execution context) + assert [ -n "$WC_ROOT" ] +} + +@test "check_wild_directory passes when in project" { + cd "$TEST_PROJECT_DIR" + run check_wild_directory + assert_success +} + +@test "print functions work correctly" { + cd "$TEST_PROJECT_DIR" + run bash -c ' + source "$PROJECT_ROOT/bin/wild-common.sh" + print_header "Test Header" + print_info "Test info message" + print_warning "Test warning message" + print_success "Test success message" + print_error "Test error message" + ' + assert_success + assert_output --partial "Test Header" + assert_output --partial "Test info message" +} + +@test "command_exists works for existing command" { + run command_exists "bash" + assert_success +} + +@test "command_exists fails for nonexistent command" { + run command_exists "nonexistent-command-xyz" + assert_failure +} + +@test "generate_random_string produces correct length" { + RANDOM_STR=$(generate_random_string 16) + assert_equal "${#RANDOM_STR}" "16" +} \ No newline at end of file diff --git a/test/test_config_functions.bats b/test/test_config_functions.bats new file mode 100644 index 0000000..39a8399 --- /dev/null +++ b/test/test_config_functions.bats @@ -0,0 +1,59 @@ +#!/usr/bin/env bats + +# test_config_functions.bats +# Tests for config and secret access functions + +load 'test_helper' + +setup() { + setup_test_project "config-test" + cd "$TEST_PROJECT_DIR" + init_wild_env +} + +teardown() { + teardown_test_project "config-test" +} + +@test "get_current_config with existing config" { + CLUSTER_NAME=$(get_current_config "cluster.name") + assert_equal "$CLUSTER_NAME" "test-cluster" +} + +@test "get_current_config with nested path" { + VIP=$(get_current_config "cluster.nodes.control.vip") + assert_equal "$VIP" "192.168.100.200" +} + +@test "get_current_config with non-existent key" { + NONEXISTENT=$(get_current_config "nonexistent.key") + assert_equal "$NONEXISTENT" "" +} + +@test "active nodes configuration access - interface" { + CONTROL_NODE_INTERFACE=$(get_current_config "cluster.nodes.active.\"192.168.100.201\".interface") + assert_equal "$CONTROL_NODE_INTERFACE" "eth0" +} + +@test "active nodes configuration access - maintenance IP" { + MAINTENANCE_IP=$(get_current_config "cluster.nodes.active.\"192.168.100.201\".maintenanceIp") + assert_equal "$MAINTENANCE_IP" "192.168.100.131" +} + +@test "get_current_secret function" { + # Create temporary secrets file for testing + cp "$TEST_DIR/fixtures/sample-secrets.yaml" "$TEST_PROJECT_DIR/secrets.yaml" + + SECRET_VAL=$(get_current_secret "operator.cloudflareApiToken") + assert_equal "$SECRET_VAL" "test_api_token_123456789" +} + +@test "config access from subdirectory" { + mkdir -p "$TEST_PROJECT_DIR/config-subdir" + cd "$TEST_PROJECT_DIR/config-subdir" + unset WC_HOME WC_ROOT + init_wild_env + + SUBDIR_CLUSTER=$(get_current_config "cluster.name") + assert_equal "$SUBDIR_CLUSTER" "test-cluster" +} \ No newline at end of file diff --git a/test/test_helper.bash b/test/test_helper.bash new file mode 100644 index 0000000..a20b720 --- /dev/null +++ b/test/test_helper.bash @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +# test_helper.bash +# Common setup and utilities for bats tests + +# Load bats helpers +load 'test_helper/bats-support/load' +load 'test_helper/bats-assert/load' + +# Test environment variables +export TEST_DIR="$(cd "$(dirname "${BATS_TEST_FILENAME}")" && pwd)" +export PROJECT_ROOT="$(dirname "$TEST_DIR")" +export TMP_DIR="$TEST_DIR/tmp" + +# Set up test environment +setup_test_project() { + local project_name="${1:-test-project}" + + # Create tmp directory + mkdir -p "$TMP_DIR" + + # Create test project + export TEST_PROJECT_DIR="$TMP_DIR/$project_name" + mkdir -p "$TEST_PROJECT_DIR/.wildcloud" + + # Copy fixture config if it exists + if [ -f "$TEST_DIR/fixtures/sample-config.yaml" ]; then + cp "$TEST_DIR/fixtures/sample-config.yaml" "$TEST_PROJECT_DIR/config.yaml" + fi + + # Source wild-common.sh + source "$PROJECT_ROOT/bin/wild-common.sh" +} + +# Clean up test environment +teardown_test_project() { + local project_name="${1:-test-project}" + + if [ -n "$TMP_DIR" ] && [ -d "$TMP_DIR" ]; then + rm -rf "$TMP_DIR/$project_name" + fi +} + +# Create additional test project +create_test_project() { + local project_name="$1" + local project_dir="$TMP_DIR/$project_name" + + mkdir -p "$project_dir/.wildcloud" + + # Copy fixture config if requested + if [ $# -gt 1 ] && [ "$2" = "with-config" ]; then + cp "$TEST_DIR/fixtures/sample-config.yaml" "$project_dir/config.yaml" + fi + + echo "$project_dir" +} + +# Remove additional test project +remove_test_project() { + local project_name="$1" + rm -rf "$TMP_DIR/$project_name" +} \ No newline at end of file diff --git a/test/test_helper/bats-assert b/test/test_helper/bats-assert new file mode 160000 index 0000000..912a988 --- /dev/null +++ b/test/test_helper/bats-assert @@ -0,0 +1 @@ +Subproject commit 912a98804efd34f24d5eae1bf97ee622ca770e99 diff --git a/test/test_helper/bats-support b/test/test_helper/bats-support new file mode 160000 index 0000000..0ad082d --- /dev/null +++ b/test/test_helper/bats-support @@ -0,0 +1 @@ +Subproject commit 0ad082d4590108684c68975ca517a90459f05cd0 diff --git a/test/test_project_detection.bats b/test/test_project_detection.bats new file mode 100644 index 0000000..a59c804 --- /dev/null +++ b/test/test_project_detection.bats @@ -0,0 +1,101 @@ +#!/usr/bin/env bats + +# test_project_detection.bats +# Tests for wild-cloud project detection from various directory structures + +load 'test_helper' + +setup() { + setup_test_project "detection-test" +} + +teardown() { + teardown_test_project "detection-test" +} + +@test "script execution from project root" { + cd "$TEST_PROJECT_DIR" + run "$PROJECT_ROOT/bin/wild-cluster-node-up" --help + assert_success +} + +@test "script execution from nested subdirectory" { + mkdir -p "$TEST_PROJECT_DIR/deep/very/nested/path" + cd "$TEST_PROJECT_DIR/deep/very/nested/path" + run "$PROJECT_ROOT/bin/wild-cluster-node-up" --help + assert_success +} + +@test "wild-cluster-node-up works from subdirectory" { + mkdir -p "$TEST_PROJECT_DIR/subdir" + cd "$TEST_PROJECT_DIR/subdir" + run "$PROJECT_ROOT/bin/wild-cluster-node-up" --help + assert_success +} + +@test "wild-setup works from subdirectory" { + mkdir -p "$TEST_PROJECT_DIR/subdir" + cd "$TEST_PROJECT_DIR/subdir" + run "$PROJECT_ROOT/bin/wild-setup" --help + assert_success +} + +@test "wild-setup-cluster works from subdirectory" { + mkdir -p "$TEST_PROJECT_DIR/subdir" + cd "$TEST_PROJECT_DIR/subdir" + run "$PROJECT_ROOT/bin/wild-setup-cluster" --help + assert_success +} + +@test "wild-cluster-config-generate works from subdirectory" { + mkdir -p "$TEST_PROJECT_DIR/subdir" + cd "$TEST_PROJECT_DIR/subdir" + run "$PROJECT_ROOT/bin/wild-cluster-config-generate" --help + assert_success +} + +@test "config access from subdirectories" { + mkdir -p "$TEST_PROJECT_DIR/config-test" + cd "$TEST_PROJECT_DIR/config-test" + + # Set up environment like the scripts do + unset WC_HOME WC_ROOT + init_wild_env + + CLUSTER_NAME=$("$PROJECT_ROOT/bin/wild-config" cluster.name 2>/dev/null) + assert_equal "$CLUSTER_NAME" "test-cluster" +} + +@test "environment variables from project root" { + cd "$TEST_PROJECT_DIR" + unset WC_HOME WC_ROOT + source "$PROJECT_ROOT/bin/wild-common.sh" + init_wild_env + + assert_equal "$WC_HOME" "$TEST_PROJECT_DIR" + assert [ -n "$WC_ROOT" ] +} + +@test "environment variables from nested directory" { + mkdir -p "$TEST_PROJECT_DIR/deep/very" + cd "$TEST_PROJECT_DIR/deep/very" + unset WC_HOME WC_ROOT + source "$PROJECT_ROOT/bin/wild-common.sh" + init_wild_env + + assert_equal "$WC_HOME" "$TEST_PROJECT_DIR" + assert [ -n "$WC_ROOT" ] +} + +@test "scripts fail gracefully outside project" { + # Create a temporary directory without .wildcloud + TEMP_NO_PROJECT=$(create_test_project "no-wildcloud") + rm -rf "$TEMP_NO_PROJECT/.wildcloud" + cd "$TEMP_NO_PROJECT" + + # The script should fail because check_wild_directory won't find .wildcloud + run "$PROJECT_ROOT/bin/wild-cluster-node-up" 192.168.1.1 --dry-run + assert_failure + + remove_test_project "no-wildcloud" +} \ No newline at end of file