Testing Astrolock changes locally
Testing
How to test Astrolock changes during development.
Adding the CLI to Your Path
- Symlink (Recommended)
- Add to PATH
- Absolute Path
# Create symlink to local bin directory
ln -s /path/to/astrolock-template/bin/astrolock ~/.local/bin/astrolock
# Ensure ~/.local/bin is in PATH
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc# Add to shell config
echo 'export PATH="/path/to/astrolock-template/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc# Run directly with full path
/path/to/astrolock-template/bin/astrolock write# Create symlink to local bin directory
ln -s /path/to/astrolock-template/bin/astrolock ~/.local/bin/astrolock
# Ensure ~/.local/bin is in PATH
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc# Add to shell config
echo 'export PATH="/path/to/astrolock-template/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc# Run directly with full path
/path/to/astrolock-template/bin/astrolock writeRunning Commands in USER_FOLDER
The CLI detects configuration based on current directory:
# Navigate to user site
cd ~/my-site
# Commands use local config automatically
astrolock write # Uses ./config, ./content, ./public
ls content/ # Lists content from ./content
astrolock deploy aws # Uses ./.astrolock/astrolock.yaml
The CLI sets environment variables based on current directory:
ASTROLOCK_CONFIG_DIR=./configASTROLOCK_CONTENT_DIR=./contentASTROLOCK_PUBLIC_DIR=./public
Testing CLI Changes
- After bashly.yml Changes
- After Script Changes
- Approval Tests
# Navigate to Astrolock source
cd /path/to/astrolock
# Regenerate CLI
bashly generate
# Verify command exists
./bin/astrolock --help
./bin/astrolock {new-command} --help
# Test command
./bin/astrolock {new-command} {args}# No regeneration needed for script changes
# Just test directly
./bin/astrolock {command} {args}# Full test suite
make test
# Just CLI tests
make test.cli
# Just approval tests
make test.approvals
# Regenerate approval baselines
make test.approvals.approve# Navigate to Astrolock source
cd /path/to/astrolock
# Regenerate CLI
bashly generate
# Verify command exists
./bin/astrolock --help
./bin/astrolock {new-command} --help
# Test command
./bin/astrolock {new-command} {args}# No regeneration needed for script changes
# Just test directly
./bin/astrolock {command} {args}# Full test suite
make test
# Just CLI tests
make test.cli
# Just approval tests
make test.approvals
# Regenerate approval baselines
make test.approvals.approveTesting Site Changes
- From PROJECT_FOLDER
- From USER_FOLDER
- Production Build
Uses default Astrolock content (documentation site):
cd /path/to/astrolock
yarn dev
# Visit http://localhost:4321Uses user’s actual content:
cd ~/my-site
astrolock write
# Visit http://localhost:4321# Build
astrolock build
# Preview built site
astrolock preview
# Visit http://localhost:4321Uses default Astrolock content (documentation site):
cd /path/to/astrolock
yarn dev
# Visit http://localhost:4321Uses user’s actual content:
cd ~/my-site
astrolock write
# Visit http://localhost:4321# Build
astrolock build
# Preview built site
astrolock preview
# Visit http://localhost:4321Testing a New Docker Image Locally
cd /path/to/astrolock
# Build image
docker build -f plugins/docker-runner/Dockerfile -t astrolock:local .
# Verify
docker images | grep astrolockcd ~/my-site
# Run dev server
docker run --rm -it \
-v $(pwd):/site \
-p 4321:4321 \
astrolock:local \
astrolock write
# Run build
docker run --rm -it \
-v $(pwd):/site \
astrolock:local \
astrolock build
# Interactive shell
docker run --rm -it \
-v $(pwd):/site \
astrolock:local \
/bin/bashdocker run --rm -it \
-v $(pwd):/site \
astrolock:local \
astrolock --help
docker run --rm -it \
-v $(pwd):/site \
astrolock:local \
ls content/Test Suite Overview
Astrolock has four types of tests:
| Type | Command | Location | Purpose |
|---|---|---|---|
| Unit Tests | yarn test | src/**/*.test.ts | TypeScript utilities |
| CLI Validation | bashly validate | cli/bashly.yml | CLI definition |
| Approval Tests | cd cli && ./test/approve | cli/test/approvals/ | CLI output verification |
| Integration Tests | make test.integration | cli/test/integration/ | End-to-end CLI workflows |
Running All Tests
- All Tests
- Quick Tests
- Integration Only
# Run everything (unit + approval + integration)
make test.all# Run unit + approval only (faster)
make test# Run BATS integration tests only
make test.integration# Run everything (unit + approval + integration)
make test.all# Run unit + approval only (faster)
make test# Run BATS integration tests only
make test.integration# TypeScript unit tests
yarn test
# CLI validation only
cd cli && bashly validate
# Approval tests only
cd cli && ./test/approve
# Integration tests only
make test.integration
# Specific integration test suite
cd cli && ./test/run-integration integration/init.bats
cd cli && ./test/run-integration integration/content.batsBATS Integration Tests
Integration tests verify end-to-end CLI workflows using BATS (Bash Automated Testing System). These tests create real temporary sites, execute actual commands, and validate the complete behavior.
Test Coverage
57 integration tests across 5 core commands:
| Test Suite | Tests | Coverage |
|---|---|---|
init.bats | 12 | Site initialization, directory structure, config creation |
content.bats | 14 | Blog posts, media items, authors, slugification |
collection-add.bats | 13 | Collection creation, media directories, configuration |
build.bats | 10 | Production builds, environment variables, targets |
deploy.bats | 8 | Platform detection, deploy configuration, flags |
Running Integration Tests
- All Integration Tests
- Specific Test Suite
- With Options
# Via Makefile (recommended)
make test.integration
# Direct execution
cd cli && ./test/run-integrationcd cli
# Run specific suite
./test/run-integration integration/init.bats
./test/run-integration integration/content.bats
./test/run-integration integration/collection-add.bats
./test/run-integration integration/build.bats
./test/run-integration integration/deploy.batscd cli
# Verbose output
./test/run-integration --verbose
# TAP format (machine-readable)
./test/run-integration --tap
# JUnit XML output
./test/run-integration --junit
# Run with pattern
./test/run-integration "integration/init.bats integration/content.bats"# Via Makefile (recommended)
make test.integration
# Direct execution
cd cli && ./test/run-integrationcd cli
# Run specific suite
./test/run-integration integration/init.bats
./test/run-integration integration/content.bats
./test/run-integration integration/collection-add.bats
./test/run-integration integration/build.bats
./test/run-integration integration/deploy.batscd cli
# Verbose output
./test/run-integration --verbose
# TAP format (machine-readable)
./test/run-integration --tap
# JUnit XML output
./test/run-integration --junit
# Run with pattern
./test/run-integration "integration/init.bats integration/content.bats"Test Structure
Integration tests follow a consistent pattern:
#!/usr/bin/env bats
# Load test helpers
load 'helpers'
setup() {
# Create isolated test environment
export TEST_TEMP_DIR="$(mktemp -d)"
export USER_ROOT="$TEST_TEMP_DIR"
cd "$TEST_TEMP_DIR"
# Initialize test site
create_initialized_site
}
teardown() {
# Clean up after test
cleanup_test_dir
}
@test "command does expected thing" {
# Arrange: Set up test conditions
# Act: Run the command
run_astrolock content blog "My Post"
# Assert: Verify results
assert_success
assert_file_exist "content/blog/my-post.md"
assert_valid_frontmatter "content/blog/my-post.md"
}
cli/test/integration/
├── helpers.bash # Shared utilities (30+ functions)
├── fixtures/ # Test data
│ ├── minimal-site.yaml # Basic config
│ ├── full-site.yaml # Complete config
│ ├── sample-blog-post.md
│ ├── sample-media-item.md
│ └── deploy-targets.yaml
├── init.bats # Site initialization tests
├── content.bats # Content creation tests
├── collection-add.bats # Collection management tests
├── build.bats # Build process tests
└── deploy.bats # Deployment testsHelper Functions
The helpers.bash library provides 30+ utility functions for common test operations:
# Create minimal initialized site
init_minimal_site
# Create full site structure with content
create_initialized_site
# Get path to CLI binary
get_cli_path
# Get path to fixture file
get_fixture_path "minimal-site.yaml"# Validate YAML file
assert_valid_yaml ".astrolock/astrolock.yaml"
# Get value from YAML
yaml_get "config.yaml" ".site.title"
# Assert YAML value equals expected
assert_yaml_equals "config.yaml" ".site.title" "My Site"
# Assert YAML path exists
assert_yaml_exists "config.yaml" ".collections.blog"
# Assert collection exists in config
assert_collection_exists "blog"
# Assert collection has content type
assert_collection_content_type "blog" "text"# Validate frontmatter in markdown
assert_valid_frontmatter "content/blog/post.md"
# Get frontmatter value
get_frontmatter_value "content/blog/post.md" "title"
# Assert frontmatter field equals expected
assert_frontmatter_equals "content/blog/post.md" "title" "My Post"
# Assert frontmatter field exists
assert_frontmatter_exists "content/blog/post.md" "date"# Run astrolock command
run_astrolock content blog "My Post"
# Run with input (for interactive commands)
run_astrolock_with_input "Site\nDescription" init --force
# Assert command succeeds
assert_astrolock_success content blog "Post"
# Assert command fails
assert_astrolock_fails content nonexistent "Post"# Assert file exists (from bats-file)
assert_file_exist "content/blog/post.md"
# Assert directory exists
assert_dir_exist "content/blog"
# Assert directory structure
assert_directory_structure \
"content/blog" \
"content/pages" \
"public/images"
# Assert file structure
assert_file_structure \
"content/blog/-index.md" \
"content/pages/about.md"# Assert date is ISO 8601 format
assert_iso8601_date "2025-12-06T12:00:00Z"
# Assert string is URL-friendly slug
assert_is_slug "my-blog-post"
# Assert output contains text
assert_output_contains "Success"
# Assert output does not contain text
assert_output_not_contains "Error"# Setup mock binary directory
setup_mock_bin
# Create mock command
mock_command "yarn" 0 # Exit code 0
# Mock with custom output
mock_command_with_output "aws" "Success" 0
# Assert mock was called
assert_mock_called "yarn"
# Assert mock was NOT called
assert_mock_not_called "netlify"
# Get mock call count
get_mock_call_count "yarn"Writing New Integration Tests
1. Choose the Right Test Suite
Add tests to existing suite or create new one:
# Add to existing suite
vim cli/test/integration/content.bats
# Create new suite
vim cli/test/integration/my-feature.bats
chmod +x cli/test/integration/my-feature.bats
2. Write Test Cases
Follow the arrange-act-assert pattern:
@test "content creates blog post with valid frontmatter" {
# Arrange: Setup test environment (done in setup())
# Act: Execute command
run_astrolock content blog "Test Post"
# Assert: Verify results
assert_success
assert_file_exist "content/blog/test-post.md"
assert_valid_frontmatter "content/blog/test-post.md"
assert_frontmatter_equals "content/blog/test-post.md" "title" "Test Post"
assert_frontmatter_equals "content/blog/test-post.md" "draft" "false"
}
3. Test Error Conditions
@test "content fails for unknown collection" {
run_astrolock content nonexistent "Test"
assert_failure
assert_output_contains "Unknown collection"
}
@test "content prevents duplicate filenames" {
# Create first post
run_astrolock content blog "Duplicate"
assert_success
# Try to create duplicate
run_astrolock content blog "Duplicate"
assert_failure
assert_output_contains "already exists"
}
4. Test Interactive Commands
For commands that read from stdin:
@test "collection add creates text collection" {
# Input format: type\nname\ndisplay\nauthor\ncta
run_astrolock_with_input "1\narticles\nArticles\nauthor\nRead" collection add
assert_success
assert_collection_exists "articles"
assert_collection_content_type "articles" "text"
}
5. Mock External Dependencies
For commands that call external tools:
setup() {
export TEST_TEMP_DIR="$(mktemp -d)"
export USER_ROOT="$TEST_TEMP_DIR"
cd "$TEST_TEMP_DIR"
create_initialized_site
# Mock yarn to avoid actual builds
setup_mock_bin
mock_command "yarn" 0
}
@test "build runs yarn install if needed" {
run_astrolock build
assert_success
assert_mock_called "yarn"
}
Test Isolation
Each test runs in complete isolation:
- Unique temp directory -
mktemp -dcreates fresh directory per test - Clean environment - No shared state between tests
- Automatic cleanup -
teardown()removes all test artifacts - Independent execution - Tests can run in any order
setup() {
# Each test gets unique directory
export TEST_TEMP_DIR="$(mktemp -d)" # /tmp/tmp.XYZ123
export USER_ROOT="$TEST_TEMP_DIR"
cd "$TEST_TEMP_DIR"
# Initialize fresh site
create_initialized_site
}
teardown() {
# Cleanup is guaranteed even if test fails
cleanup_test_dir # Removes $TEST_TEMP_DIR
}This ensures:
- Tests never interfere with each other
- Tests never modify your actual filesystem
- Failed tests don’t leave artifacts
- Tests produce consistent results
Debugging Integration Tests
cd cli
# Run just one test file
./test/run-integration integration/init.bats
# BATS doesn't support running individual @test,
# but you can temporarily comment out other testscd cli
# See detailed output from commands
./test/run-integration --verbose integration/init.bats
# See BATS internal debugging
./test/run-integration --trace integration/init.bats@test "debug by inspecting files" {
run_astrolock init --force <<< "Test\nSite"
# Print directory tree to BATS output (fd 3)
debug_print_tree >&3
# Print file contents
debug_print_file ".astrolock/astrolock.yaml" >&3
assert_success
}# Temporarily disable cleanup to inspect files
teardown() {
echo "Test dir: $TEST_TEMP_DIR" >&3
# Comment out: cleanup_test_dir
}
# Run test, then inspect directory manually
cd $TEST_TEMP_DIR # From test output
ls -la
cat .astrolock/astrolock.yamlInstalling BATS Dependencies
Integration tests require BATS and helper libraries:
- macOS (Homebrew)
- Linux (Git Submodules)
- Verify Installation
# Install BATS
brew install bats-core
# Install helper libraries
brew tap kaos/shell
brew install bats-assert bats-support bats-filecd cli/test
# Add as git submodules
git submodule add https://github.com/bats-core/bats-core.git lib/bats
git submodule add https://github.com/bats-core/bats-support.git lib/bats-support
git submodule add https://github.com/bats-core/bats-assert.git lib/bats-assert
git submodule add https://github.com/ztombol/bats-file.git lib/bats-file
# Update helpers.bash to load from lib/# Check BATS version
bats --version
# Check helper libraries
ls /opt/homebrew/lib/bats-*
# Run test runner dependency check
cd cli && ./test/run-integration --help# Install BATS
brew install bats-core
# Install helper libraries
brew tap kaos/shell
brew install bats-assert bats-support bats-filecd cli/test
# Add as git submodules
git submodule add https://github.com/bats-core/bats-core.git lib/bats
git submodule add https://github.com/bats-core/bats-support.git lib/bats-support
git submodule add https://github.com/bats-core/bats-assert.git lib/bats-assert
git submodule add https://github.com/ztombol/bats-file.git lib/bats-file
# Update helpers.bash to load from lib/# Check BATS version
bats --version
# Check helper libraries
ls /opt/homebrew/lib/bats-*
# Run test runner dependency check
cd cli && ./test/run-integration --helpBest Practices
# Good ✓
@test "init creates .astrolock/astrolock.yaml with valid structure"
@test "content fails when collection does not exist"
# Bad ✗
@test "test1"
@test "it works"# Good ✓
@test "content creates file with correct filename" {
run_astrolock content blog "My Post"
assert_file_exist "content/blog/my-post.md"
}
@test "content creates file with valid frontmatter" {
run_astrolock content blog "My Post"
assert_valid_frontmatter "content/blog/my-post.md"
}
# Bad ✗
@test "content creates everything correctly" {
# Tests too many things at once
run_astrolock content blog "My Post"
assert_file_exist "content/blog/my-post.md"
assert_valid_frontmatter "content/blog/my-post.md"
assert_frontmatter_equals "content/blog/my-post.md" "title" "My Post"
assert_frontmatter_equals "content/blog/my-post.md" "draft" "false"
assert_dir_exist "content/blog"
# ... 20 more assertions
}# Good ✓
assert_file_exist "content/blog/post.md"
assert_valid_yaml ".astrolock/astrolock.yaml"
assert_frontmatter_equals "post.md" "title" "My Post"
# Bad ✗
[ -f "content/blog/post.md" ] || fail "File missing"
yq eval '.' ".astrolock/astrolock.yaml" || fail "Invalid YAML"
grep "title: My Post" post.md || fail "Title wrong"# Good ✓
setup() {
# ...
setup_mock_bin
mock_command "yarn" 0 # Mock expensive builds
mock_command "aws" 0 # Mock cloud CLI
mock_command "netlify" 0 # Mock deployment
}
# Bad ✗
@test "deploy to AWS" {
# Actually calls AWS CLI - slow, expensive, requires credentials
run_astrolock deploy production --execute
}Creating Test Sites
- Quick Test Site
- Full Test Site
# Create temporary directory
mkdir ~/test-site && cd ~/test-site
# Initialize with Astrolock
astrolock init --defaults
# Test
astrolock writemkdir ~/test-site && cd ~/test-site
# Run full wizard
astrolock init
# Add test content
astrolock content blog "Test Post"
astrolock content mixes "Test Mix"
# Copy test media files
cp ~/test-audio.mp3 public/audio/content/mixes/test-mix.mp3
# Test
astrolock write# Create temporary directory
mkdir ~/test-site && cd ~/test-site
# Initialize with Astrolock
astrolock init --defaults
# Test
astrolock writemkdir ~/test-site && cd ~/test-site
# Run full wizard
astrolock init
# Add test content
astrolock content blog "Test Post"
astrolock content mixes "Test Mix"
# Copy test media files
cp ~/test-audio.mp3 public/audio/content/mixes/test-mix.mp3
# Test
astrolock writeDebugging Tips
export astrolock_debug=1
astrolock {command}# See what config is being loaded
astrolock build --verbose# Check compiled CLI
less bin/astrolock
# Check generated templates
less cli/lib/templates.shCommon Issues
- Command Not Found
- Config Not Loading
- Style Changes Not Appearing
- Docker Using Old Code
After bashly changes:
bashly generate # Regenerate CLI# Check you're in the right directory
pwd
ls config/ # Should see astrolock.yaml# Hard refresh browser (Cmd+Shift+R)
# Or restart dev server# Rebuild without cache
docker build --no-cache -f plugins/docker-runner/Dockerfile -t astrolock:local .After bashly changes:
bashly generate # Regenerate CLI# Check you're in the right directory
pwd
ls config/ # Should see astrolock.yaml# Hard refresh browser (Cmd+Shift+R)
# Or restart dev server# Rebuild without cache
docker build --no-cache -f plugins/docker-runner/Dockerfile -t astrolock:local .