mirror of
https://github.com/ivuorinen/actions.git
synced 2026-02-03 14:42:41 +00:00
refactor: remove common-cache action
Delete common-cache action and all associated test files. All actions now use native actions/cache@v4.3.0 instead of the wrapper. Deleted: - common-cache/action.yml - common-cache/README.md - common-cache/rules.yml - common-cache/CustomValidator.py - _tests/unit/common-cache/validation.spec.sh - _tests/integration/workflows/common-cache-test.yml - validate-inputs/tests/test_common-cache_custom.py Action count: 28 → 27
This commit is contained in:
@@ -1,471 +0,0 @@
|
||||
---
|
||||
name: Integration Test - Common Cache
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'common-cache/**'
|
||||
- '_tests/integration/workflows/common-cache-test.yml'
|
||||
|
||||
jobs:
|
||||
test-common-cache-key-generation:
|
||||
name: Test Cache Key Generation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test basic key generation
|
||||
run: |
|
||||
RUNNER_OS="Linux"
|
||||
CACHE_TYPE="npm"
|
||||
KEY_PREFIX=""
|
||||
|
||||
cache_key="$RUNNER_OS"
|
||||
[ -n "$CACHE_TYPE" ] && cache_key="${cache_key}-${CACHE_TYPE}"
|
||||
|
||||
expected="Linux-npm"
|
||||
if [[ "$cache_key" != "$expected" ]]; then
|
||||
echo "❌ ERROR: Expected '$expected', got '$cache_key'"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Basic cache key generation works"
|
||||
|
||||
- name: Test key with prefix
|
||||
run: |
|
||||
RUNNER_OS="Linux"
|
||||
CACHE_TYPE="npm"
|
||||
KEY_PREFIX="node-20"
|
||||
|
||||
cache_key="$RUNNER_OS"
|
||||
[ -n "$KEY_PREFIX" ] && cache_key="${cache_key}-${KEY_PREFIX}"
|
||||
[ -n "$CACHE_TYPE" ] && cache_key="${cache_key}-${CACHE_TYPE}"
|
||||
|
||||
expected="Linux-node-20-npm"
|
||||
if [[ "$cache_key" != "$expected" ]]; then
|
||||
echo "❌ ERROR: Expected '$expected', got '$cache_key'"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Cache key with prefix works"
|
||||
|
||||
- name: Test OS-specific keys
|
||||
run: |
|
||||
for os in "Linux" "macOS" "Windows"; do
|
||||
CACHE_TYPE="test"
|
||||
cache_key="$os-$CACHE_TYPE"
|
||||
if [[ ! "$cache_key" =~ ^(Linux|macOS|Windows)-test$ ]]; then
|
||||
echo "❌ ERROR: Invalid key for OS $os: $cache_key"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ OS-specific key for $os: $cache_key"
|
||||
done
|
||||
|
||||
test-common-cache-file-hashing:
|
||||
name: Test File Hashing
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create test files
|
||||
run: |
|
||||
mkdir -p test-cache
|
||||
cd test-cache
|
||||
echo "content1" > file1.txt
|
||||
echo "content2" > file2.txt
|
||||
echo "content3" > file3.txt
|
||||
|
||||
- name: Test single file hash
|
||||
run: |
|
||||
cd test-cache
|
||||
file_hash=$(cat file1.txt | sha256sum | cut -d' ' -f1)
|
||||
|
||||
if [[ ! "$file_hash" =~ ^[a-f0-9]{64}$ ]]; then
|
||||
echo "❌ ERROR: Invalid hash format: $file_hash"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Single file hash: $file_hash"
|
||||
|
||||
- name: Test multiple file hash
|
||||
run: |
|
||||
cd test-cache
|
||||
multi_hash=$(cat file1.txt file2.txt file3.txt | sha256sum | cut -d' ' -f1)
|
||||
|
||||
if [[ ! "$multi_hash" =~ ^[a-f0-9]{64}$ ]]; then
|
||||
echo "❌ ERROR: Invalid hash format: $multi_hash"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Multiple file hash: $multi_hash"
|
||||
|
||||
- name: Test hash changes with content
|
||||
run: |
|
||||
cd test-cache
|
||||
|
||||
# Get initial hash
|
||||
hash1=$(cat file1.txt | sha256sum | cut -d' ' -f1)
|
||||
|
||||
# Modify file
|
||||
echo "modified" > file1.txt
|
||||
|
||||
# Get new hash
|
||||
hash2=$(cat file1.txt | sha256sum | cut -d' ' -f1)
|
||||
|
||||
if [[ "$hash1" == "$hash2" ]]; then
|
||||
echo "❌ ERROR: Hash should change when content changes"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Hash changes with content modification"
|
||||
|
||||
- name: Test comma-separated file list processing
|
||||
run: |
|
||||
cd test-cache
|
||||
|
||||
KEY_FILES="file1.txt,file2.txt,file3.txt"
|
||||
IFS=',' read -ra FILES <<< "$KEY_FILES"
|
||||
|
||||
existing_files=()
|
||||
for file in "${FILES[@]}"; do
|
||||
file=$(echo "$file" | xargs)
|
||||
if [ -f "$file" ]; then
|
||||
existing_files+=("$file")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#existing_files[@]} -ne 3 ]; then
|
||||
echo "❌ ERROR: Should find 3 files, found ${#existing_files[@]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Comma-separated file list processing works"
|
||||
|
||||
- name: Test missing file handling
|
||||
run: |
|
||||
cd test-cache
|
||||
|
||||
KEY_FILES="file1.txt,missing.txt,file2.txt"
|
||||
IFS=',' read -ra FILES <<< "$KEY_FILES"
|
||||
|
||||
existing_files=()
|
||||
for file in "${FILES[@]}"; do
|
||||
file=$(echo "$file" | xargs)
|
||||
if [ -f "$file" ]; then
|
||||
existing_files+=("$file")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#existing_files[@]} -ne 2 ]; then
|
||||
echo "❌ ERROR: Should find 2 files, found ${#existing_files[@]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Missing files correctly skipped"
|
||||
|
||||
test-common-cache-env-vars:
|
||||
name: Test Environment Variables
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test single env var inclusion
|
||||
run: |
|
||||
export NODE_VERSION="20.9.0"
|
||||
ENV_VARS="NODE_VERSION"
|
||||
|
||||
IFS=',' read -ra VARS <<< "$ENV_VARS"
|
||||
env_hash=""
|
||||
for var in "${VARS[@]}"; do
|
||||
if [ -n "${!var}" ]; then
|
||||
env_hash="${env_hash}-${var}-${!var}"
|
||||
fi
|
||||
done
|
||||
|
||||
expected="-NODE_VERSION-20.9.0"
|
||||
if [[ "$env_hash" != "$expected" ]]; then
|
||||
echo "❌ ERROR: Expected '$expected', got '$env_hash'"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Single env var inclusion works"
|
||||
|
||||
- name: Test multiple env vars
|
||||
run: |
|
||||
export NODE_VERSION="20.9.0"
|
||||
export PACKAGE_MANAGER="npm"
|
||||
ENV_VARS="NODE_VERSION,PACKAGE_MANAGER"
|
||||
|
||||
IFS=',' read -ra VARS <<< "$ENV_VARS"
|
||||
env_hash=""
|
||||
for var in "${VARS[@]}"; do
|
||||
if [ -n "${!var}" ]; then
|
||||
env_hash="${env_hash}-${var}-${!var}"
|
||||
fi
|
||||
done
|
||||
|
||||
expected="-NODE_VERSION-20.9.0-PACKAGE_MANAGER-npm"
|
||||
if [[ "$env_hash" != "$expected" ]]; then
|
||||
echo "❌ ERROR: Expected '$expected', got '$env_hash'"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Multiple env vars inclusion works"
|
||||
|
||||
- name: Test undefined env var skipping
|
||||
run: |
|
||||
export NODE_VERSION="20.9.0"
|
||||
ENV_VARS="NODE_VERSION,UNDEFINED_VAR"
|
||||
|
||||
IFS=',' read -ra VARS <<< "$ENV_VARS"
|
||||
env_hash=""
|
||||
for var in "${VARS[@]}"; do
|
||||
if [ -n "${!var}" ]; then
|
||||
env_hash="${env_hash}-${var}-${!var}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Should only include NODE_VERSION
|
||||
expected="-NODE_VERSION-20.9.0"
|
||||
if [[ "$env_hash" != "$expected" ]]; then
|
||||
echo "❌ ERROR: Expected '$expected', got '$env_hash'"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Undefined env vars correctly skipped"
|
||||
|
||||
test-common-cache-path-processing:
|
||||
name: Test Path Processing
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test single path
|
||||
run: |
|
||||
CACHE_PATHS="~/.npm"
|
||||
IFS=',' read -ra PATHS <<< "$CACHE_PATHS"
|
||||
|
||||
if [ ${#PATHS[@]} -ne 1 ]; then
|
||||
echo "❌ ERROR: Should have 1 path, got ${#PATHS[@]}"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Single path processing works"
|
||||
|
||||
- name: Test multiple paths
|
||||
run: |
|
||||
CACHE_PATHS="~/.npm,~/.yarn/cache,node_modules"
|
||||
IFS=',' read -ra PATHS <<< "$CACHE_PATHS"
|
||||
|
||||
if [ ${#PATHS[@]} -ne 3 ]; then
|
||||
echo "❌ ERROR: Should have 3 paths, got ${#PATHS[@]}"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Multiple paths processing works"
|
||||
|
||||
- name: Test path with spaces (trimming)
|
||||
run: |
|
||||
CACHE_PATHS=" ~/.npm , ~/.yarn/cache , node_modules "
|
||||
IFS=',' read -ra PATHS <<< "$CACHE_PATHS"
|
||||
|
||||
trimmed_paths=()
|
||||
for path in "${PATHS[@]}"; do
|
||||
trimmed=$(echo "$path" | xargs)
|
||||
trimmed_paths+=("$trimmed")
|
||||
done
|
||||
|
||||
# Check first path is trimmed
|
||||
if [[ "${trimmed_paths[0]}" != "~/.npm" ]]; then
|
||||
echo "❌ ERROR: Path not trimmed: '${trimmed_paths[0]}'"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Path trimming works"
|
||||
|
||||
test-common-cache-complete-key-generation:
|
||||
name: Test Complete Key Generation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create test files
|
||||
run: |
|
||||
mkdir -p test-complete
|
||||
cd test-complete
|
||||
echo "package-lock content" > package-lock.json
|
||||
|
||||
- name: Test complete cache key with all components
|
||||
run: |
|
||||
cd test-complete
|
||||
|
||||
RUNNER_OS="Linux"
|
||||
CACHE_TYPE="npm"
|
||||
KEY_PREFIX="node-20"
|
||||
|
||||
# Generate file hash
|
||||
files_hash=$(cat package-lock.json | sha256sum | cut -d' ' -f1)
|
||||
|
||||
# Generate env hash
|
||||
export NODE_VERSION="20.9.0"
|
||||
env_hash="-NODE_VERSION-20.9.0"
|
||||
|
||||
# Generate final key
|
||||
cache_key="$RUNNER_OS"
|
||||
[ -n "$KEY_PREFIX" ] && cache_key="${cache_key}-${KEY_PREFIX}"
|
||||
[ -n "$CACHE_TYPE" ] && cache_key="${cache_key}-${CACHE_TYPE}"
|
||||
[ -n "$files_hash" ] && cache_key="${cache_key}-${files_hash}"
|
||||
[ -n "$env_hash" ] && cache_key="${cache_key}${env_hash}"
|
||||
|
||||
echo "Generated cache key: $cache_key"
|
||||
|
||||
# Verify structure
|
||||
if [[ ! "$cache_key" =~ ^Linux-node-20-npm-[a-f0-9]{64}-NODE_VERSION-20\.9\.0$ ]]; then
|
||||
echo "❌ ERROR: Invalid cache key structure: $cache_key"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Complete cache key generation works"
|
||||
|
||||
test-common-cache-restore-keys:
|
||||
name: Test Restore Keys
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test single restore key
|
||||
run: |
|
||||
RESTORE_KEYS="Linux-npm-"
|
||||
|
||||
if [[ -z "$RESTORE_KEYS" ]]; then
|
||||
echo "❌ ERROR: Restore keys should not be empty"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Single restore key: $RESTORE_KEYS"
|
||||
|
||||
- name: Test multiple restore keys
|
||||
run: |
|
||||
RESTORE_KEYS="Linux-node-20-npm-,Linux-node-npm-,Linux-npm-"
|
||||
|
||||
IFS=',' read -ra KEYS <<< "$RESTORE_KEYS"
|
||||
if [ ${#KEYS[@]} -ne 3 ]; then
|
||||
echo "❌ ERROR: Should have 3 restore keys, got ${#KEYS[@]}"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Multiple restore keys work"
|
||||
|
||||
test-common-cache-type-specific-scenarios:
|
||||
name: Test Type-Specific Scenarios
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test NPM cache key
|
||||
run: |
|
||||
TYPE="npm"
|
||||
FILES="package-lock.json"
|
||||
PATHS="~/.npm,node_modules"
|
||||
|
||||
echo "✓ NPM cache configuration valid"
|
||||
echo " Type: $TYPE"
|
||||
echo " Key files: $FILES"
|
||||
echo " Paths: $PATHS"
|
||||
|
||||
- name: Test Composer cache key
|
||||
run: |
|
||||
TYPE="composer"
|
||||
FILES="composer.lock"
|
||||
PATHS="~/.composer/cache,vendor"
|
||||
|
||||
echo "✓ Composer cache configuration valid"
|
||||
echo " Type: $TYPE"
|
||||
echo " Key files: $FILES"
|
||||
echo " Paths: $PATHS"
|
||||
|
||||
- name: Test Go cache key
|
||||
run: |
|
||||
TYPE="go"
|
||||
FILES="go.sum"
|
||||
PATHS="~/go/pkg/mod,~/.cache/go-build"
|
||||
|
||||
echo "✓ Go cache configuration valid"
|
||||
echo " Type: $TYPE"
|
||||
echo " Key files: $FILES"
|
||||
echo " Paths: $PATHS"
|
||||
|
||||
- name: Test Pip cache key
|
||||
run: |
|
||||
TYPE="pip"
|
||||
FILES="requirements.txt"
|
||||
PATHS="~/.cache/pip"
|
||||
|
||||
echo "✓ Pip cache configuration valid"
|
||||
echo " Type: $TYPE"
|
||||
echo " Key files: $FILES"
|
||||
echo " Paths: $PATHS"
|
||||
|
||||
test-common-cache-edge-cases:
|
||||
name: Test Edge Cases
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test empty prefix
|
||||
run: |
|
||||
KEY_PREFIX=""
|
||||
cache_key="Linux"
|
||||
[ -n "$KEY_PREFIX" ] && cache_key="${cache_key}-${KEY_PREFIX}"
|
||||
|
||||
if [[ "$cache_key" != "Linux" ]]; then
|
||||
echo "❌ ERROR: Empty prefix should not modify key"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Empty prefix handling works"
|
||||
|
||||
- name: Test no key files
|
||||
run: |
|
||||
KEY_FILES=""
|
||||
files_hash=""
|
||||
|
||||
if [ -n "$KEY_FILES" ]; then
|
||||
echo "❌ ERROR: Should detect empty key files"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ No key files handling works"
|
||||
|
||||
- name: Test no env vars
|
||||
run: |
|
||||
ENV_VARS=""
|
||||
env_hash=""
|
||||
|
||||
if [ -n "$ENV_VARS" ]; then
|
||||
echo "❌ ERROR: Should detect empty env vars"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ No env vars handling works"
|
||||
|
||||
integration-test-summary:
|
||||
name: Integration Test Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test-common-cache-key-generation
|
||||
- test-common-cache-file-hashing
|
||||
- test-common-cache-env-vars
|
||||
- test-common-cache-path-processing
|
||||
- test-common-cache-complete-key-generation
|
||||
- test-common-cache-restore-keys
|
||||
- test-common-cache-type-specific-scenarios
|
||||
- test-common-cache-edge-cases
|
||||
steps:
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "=========================================="
|
||||
echo "Common Cache Integration Tests - PASSED"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "✓ Cache key generation tests"
|
||||
echo "✓ File hashing tests"
|
||||
echo "✓ Environment variable tests"
|
||||
echo "✓ Path processing tests"
|
||||
echo "✓ Complete key generation tests"
|
||||
echo "✓ Restore keys tests"
|
||||
echo "✓ Type-specific scenario tests"
|
||||
echo "✓ Edge case tests"
|
||||
echo ""
|
||||
echo "All common-cache integration tests completed successfully!"
|
||||
@@ -1,168 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for common-cache action validation and logic
|
||||
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "common-cache action"
|
||||
ACTION_DIR="common-cache"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating cache type input"
|
||||
It "accepts npm cache type"
|
||||
When call validate_input_python "common-cache" "type" "npm"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts composer cache type"
|
||||
When call validate_input_python "common-cache" "type" "composer"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts go cache type"
|
||||
When call validate_input_python "common-cache" "type" "go"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts pip cache type"
|
||||
When call validate_input_python "common-cache" "type" "pip"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts maven cache type"
|
||||
When call validate_input_python "common-cache" "type" "maven"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts gradle cache type"
|
||||
When call validate_input_python "common-cache" "type" "gradle"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects empty cache type"
|
||||
When call validate_input_python "common-cache" "type" ""
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects invalid cache type"
|
||||
Pending "TODO: Implement enum validation for cache type"
|
||||
When call validate_input_python "common-cache" "type" "invalid-type"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating paths input"
|
||||
It "accepts single path"
|
||||
When call validate_input_python "common-cache" "paths" "node_modules"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts multiple paths"
|
||||
When call validate_input_python "common-cache" "paths" "node_modules,dist,build"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects empty paths"
|
||||
When call validate_input_python "common-cache" "paths" ""
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects path traversal"
|
||||
When call validate_input_python "common-cache" "paths" "../../../etc/passwd"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects command injection in paths"
|
||||
When call validate_input_python "common-cache" "paths" "node_modules;rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating key-prefix input"
|
||||
It "accepts valid key prefix"
|
||||
When call validate_input_python "common-cache" "key-prefix" "v2-build"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects command injection in key-prefix"
|
||||
When call validate_input_python "common-cache" "key-prefix" "v2&&malicious"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating key-files input"
|
||||
It "accepts single key file"
|
||||
When call validate_input_python "common-cache" "key-files" "package.json"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts multiple key files"
|
||||
When call validate_input_python "common-cache" "key-files" "package.json,package-lock.json,yarn.lock"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects path traversal in key-files"
|
||||
When call validate_input_python "common-cache" "key-files" "../../../sensitive.json"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating restore-keys input"
|
||||
It "accepts valid restore keys format"
|
||||
When call validate_input_python "common-cache" "restore-keys" "Linux-npm-,Linux-"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects malicious restore keys"
|
||||
When call validate_input_python "common-cache" "restore-keys" "Linux-npm-;rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "Common Cache"
|
||||
End
|
||||
|
||||
It "defines required inputs"
|
||||
inputs=$(get_action_inputs "$ACTION_FILE")
|
||||
When call echo "$inputs"
|
||||
The output should include "type"
|
||||
The output should include "paths"
|
||||
End
|
||||
|
||||
It "defines optional inputs"
|
||||
inputs=$(get_action_inputs "$ACTION_FILE")
|
||||
When call echo "$inputs"
|
||||
The output should include "key-prefix"
|
||||
The output should include "key-files"
|
||||
The output should include "restore-keys"
|
||||
The output should include "env-vars"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
outputs=$(get_action_outputs "$ACTION_FILE")
|
||||
When call echo "$outputs"
|
||||
The output should include "cache-hit"
|
||||
The output should include "cache-key"
|
||||
The output should include "cache-paths"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating security"
|
||||
It "rejects injection in all input types"
|
||||
When call validate_input_python "common-cache" "type" "npm;malicious"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates environment variable names safely"
|
||||
When call validate_input_python "common-cache" "env-vars" "NODE_ENV,CI"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects injection in environment variables"
|
||||
When call validate_input_python "common-cache" "env-vars" "NODE_ENV;rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing outputs"
|
||||
It "produces all expected outputs consistently"
|
||||
When call test_action_outputs "$ACTION_DIR" "type" "npm" "paths" "node_modules"
|
||||
The status should be success
|
||||
The stderr should include "Testing action outputs for: common-cache"
|
||||
The stderr should include "Output test passed for: common-cache"
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,244 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Custom validator for common-cache action.
|
||||
|
||||
This validator handles caching-specific validation including:
|
||||
- Cache types (npm, composer, go, pip, maven, gradle)
|
||||
- Cache paths (comma-separated list)
|
||||
- Cache keys and restore keys
|
||||
- Path validation with special handling for multiple paths
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
# Add validate-inputs directory to path to import validators
|
||||
validate_inputs_path = Path(__file__).parent.parent / "validate-inputs"
|
||||
sys.path.insert(0, str(validate_inputs_path))
|
||||
|
||||
from validators.base import BaseValidator
|
||||
from validators.file import FileValidator
|
||||
|
||||
|
||||
class CustomValidator(BaseValidator):
|
||||
"""Custom validator for common-cache action.
|
||||
|
||||
Provides validation for cache configuration.
|
||||
"""
|
||||
|
||||
def __init__(self, action_type: str = "common-cache") -> None:
|
||||
"""Initialize the common-cache validator."""
|
||||
super().__init__(action_type)
|
||||
self.file_validator = FileValidator(action_type)
|
||||
|
||||
def validate_inputs(self, inputs: dict[str, str]) -> bool:
|
||||
"""Validate common-cache specific inputs.
|
||||
|
||||
Args:
|
||||
inputs: Dictionary of input names to values
|
||||
|
||||
Returns:
|
||||
True if all validations pass, False otherwise
|
||||
"""
|
||||
valid = True
|
||||
|
||||
# Validate type (required)
|
||||
if "type" in inputs:
|
||||
valid &= self.validate_cache_type(inputs["type"])
|
||||
else:
|
||||
# Type is required
|
||||
self.add_error("Cache type is required")
|
||||
valid = False
|
||||
|
||||
# Validate paths (required)
|
||||
if "paths" in inputs:
|
||||
valid &= self.validate_cache_paths(inputs["paths"])
|
||||
else:
|
||||
# Paths is required
|
||||
self.add_error("Cache paths are required")
|
||||
valid = False
|
||||
|
||||
# Validate key-prefix (optional)
|
||||
if inputs.get("key-prefix"):
|
||||
valid &= self.validate_key_prefix(inputs["key-prefix"])
|
||||
|
||||
# Validate key-files (optional)
|
||||
if inputs.get("key-files"):
|
||||
valid &= self.validate_key_files(inputs["key-files"])
|
||||
|
||||
# Validate restore-keys (optional)
|
||||
if inputs.get("restore-keys"):
|
||||
valid &= self.validate_restore_keys(inputs["restore-keys"])
|
||||
|
||||
# Validate env-vars (optional)
|
||||
if inputs.get("env-vars"):
|
||||
valid &= self.validate_env_vars(inputs["env-vars"])
|
||||
|
||||
return valid
|
||||
|
||||
def get_required_inputs(self) -> list[str]:
|
||||
"""Get list of required inputs for common-cache.
|
||||
|
||||
Returns:
|
||||
List of required input names
|
||||
"""
|
||||
return ["type", "paths"]
|
||||
|
||||
def get_validation_rules(self) -> dict:
|
||||
"""Get validation rules for common-cache.
|
||||
|
||||
Returns:
|
||||
Dictionary of validation rules
|
||||
"""
|
||||
return {
|
||||
"type": "Cache type (npm, composer, go, pip, maven, gradle)",
|
||||
"paths": "Comma-separated list of paths to cache",
|
||||
"key-prefix": "Optional prefix for cache key",
|
||||
"key-files": "Files to include in cache key hash",
|
||||
"restore-keys": "Fallback cache keys to try",
|
||||
}
|
||||
|
||||
def validate_cache_type(self, cache_type: str) -> bool:
|
||||
"""Validate cache type.
|
||||
|
||||
Args:
|
||||
cache_type: Type of cache
|
||||
|
||||
Returns:
|
||||
True if valid, False otherwise
|
||||
"""
|
||||
# Check for empty
|
||||
if not cache_type or not cache_type.strip():
|
||||
self.add_error("Cache type cannot be empty")
|
||||
return False
|
||||
|
||||
# Allow GitHub Actions expressions
|
||||
if self.is_github_expression(cache_type):
|
||||
return True
|
||||
|
||||
# Note: The test says "accepts invalid cache type (no validation in action)"
|
||||
# This suggests we should accept any value, not just the supported ones
|
||||
# So we'll just validate for security issues, not restrict to specific types
|
||||
|
||||
# Check for command injection using base validator
|
||||
return self.validate_security_patterns(cache_type, "cache type")
|
||||
|
||||
def validate_cache_paths(self, paths: str) -> bool:
|
||||
"""Validate cache paths (comma-separated).
|
||||
|
||||
Args:
|
||||
paths: Comma-separated paths
|
||||
|
||||
Returns:
|
||||
True if valid, False otherwise
|
||||
"""
|
||||
# Check for empty
|
||||
if not paths or not paths.strip():
|
||||
self.add_error("Cache paths cannot be empty")
|
||||
return False
|
||||
|
||||
# Allow GitHub Actions expressions
|
||||
if self.is_github_expression(paths):
|
||||
return True
|
||||
|
||||
# Split paths and validate each
|
||||
path_list = [p.strip() for p in paths.split(",")]
|
||||
|
||||
for path in path_list:
|
||||
if not path:
|
||||
continue
|
||||
|
||||
# Use FileValidator for path validation
|
||||
result = self.file_validator.validate_file_path(path, "paths")
|
||||
# Propagate errors from file validator
|
||||
for error in self.file_validator.errors:
|
||||
if error not in self.errors:
|
||||
self.add_error(error)
|
||||
self.file_validator.clear_errors()
|
||||
|
||||
if not result:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def validate_key_prefix(self, key_prefix: str) -> bool:
|
||||
"""Validate cache key prefix.
|
||||
|
||||
Args:
|
||||
key_prefix: Key prefix
|
||||
|
||||
Returns:
|
||||
True if valid, False otherwise
|
||||
"""
|
||||
# Allow GitHub Actions expressions
|
||||
if self.is_github_expression(key_prefix):
|
||||
return True
|
||||
|
||||
# Check for command injection using base validator
|
||||
return self.validate_security_patterns(key_prefix, "key-prefix")
|
||||
|
||||
def validate_key_files(self, key_files: str) -> bool:
|
||||
"""Validate key files (comma-separated).
|
||||
|
||||
Args:
|
||||
key_files: Comma-separated file paths
|
||||
|
||||
Returns:
|
||||
True if valid, False otherwise
|
||||
"""
|
||||
# Allow GitHub Actions expressions
|
||||
if self.is_github_expression(key_files):
|
||||
return True
|
||||
|
||||
# Split files and validate each
|
||||
file_list = [f.strip() for f in key_files.split(",")]
|
||||
|
||||
for file_path in file_list:
|
||||
if not file_path:
|
||||
continue
|
||||
|
||||
# Use FileValidator for path validation
|
||||
result = self.file_validator.validate_file_path(file_path, "key-files")
|
||||
# Propagate errors from file validator
|
||||
for error in self.file_validator.errors:
|
||||
if error not in self.errors:
|
||||
self.add_error(error)
|
||||
self.file_validator.clear_errors()
|
||||
|
||||
if not result:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def validate_restore_keys(self, restore_keys: str) -> bool:
|
||||
"""Validate restore keys.
|
||||
|
||||
Args:
|
||||
restore_keys: Restore keys specification
|
||||
|
||||
Returns:
|
||||
True if valid, False otherwise
|
||||
"""
|
||||
# Allow GitHub Actions expressions
|
||||
if self.is_github_expression(restore_keys):
|
||||
return True
|
||||
|
||||
# Check for command injection using base validator
|
||||
return self.validate_security_patterns(restore_keys, "restore-keys")
|
||||
|
||||
def validate_env_vars(self, env_vars: str) -> bool:
|
||||
"""Validate environment variables.
|
||||
|
||||
Args:
|
||||
env_vars: Environment variables specification
|
||||
|
||||
Returns:
|
||||
True if valid, False otherwise
|
||||
"""
|
||||
# Allow GitHub Actions expressions
|
||||
if self.is_github_expression(env_vars):
|
||||
return True
|
||||
|
||||
# Check for command injection using base validator
|
||||
return self.validate_security_patterns(env_vars, "env-vars")
|
||||
@@ -1,72 +0,0 @@
|
||||
# ivuorinen/actions/common-cache
|
||||
|
||||
## Common Cache
|
||||
|
||||
### Description
|
||||
|
||||
Standardized caching strategy for all actions
|
||||
|
||||
### Inputs
|
||||
|
||||
| name | description | required | default |
|
||||
|----------------|------------------------------------------------------|----------|---------|
|
||||
| `type` | <p>Type of cache (npm, composer, go, pip, etc.)</p> | `true` | `""` |
|
||||
| `paths` | <p>Paths to cache (comma-separated)</p> | `true` | `""` |
|
||||
| `key-prefix` | <p>Custom prefix for cache key</p> | `false` | `""` |
|
||||
| `key-files` | <p>Files to hash for cache key (comma-separated)</p> | `false` | `""` |
|
||||
| `restore-keys` | <p>Fallback keys for cache restoration</p> | `false` | `""` |
|
||||
| `env-vars` | <p>Environment variables to include in cache key</p> | `false` | `""` |
|
||||
|
||||
### Outputs
|
||||
|
||||
| name | description |
|
||||
|---------------|-----------------------------|
|
||||
| `cache-hit` | <p>Cache hit indicator</p> |
|
||||
| `cache-key` | <p>Generated cache key</p> |
|
||||
| `cache-paths` | <p>Resolved cache paths</p> |
|
||||
|
||||
### Runs
|
||||
|
||||
This action is a `composite` action.
|
||||
|
||||
### Usage
|
||||
|
||||
```yaml
|
||||
- uses: ivuorinen/actions/common-cache@main
|
||||
with:
|
||||
type:
|
||||
# Type of cache (npm, composer, go, pip, etc.)
|
||||
#
|
||||
# Required: true
|
||||
# Default: ""
|
||||
|
||||
paths:
|
||||
# Paths to cache (comma-separated)
|
||||
#
|
||||
# Required: true
|
||||
# Default: ""
|
||||
|
||||
key-prefix:
|
||||
# Custom prefix for cache key
|
||||
#
|
||||
# Required: false
|
||||
# Default: ""
|
||||
|
||||
key-files:
|
||||
# Files to hash for cache key (comma-separated)
|
||||
#
|
||||
# Required: false
|
||||
# Default: ""
|
||||
|
||||
restore-keys:
|
||||
# Fallback keys for cache restoration
|
||||
#
|
||||
# Required: false
|
||||
# Default: ""
|
||||
|
||||
env-vars:
|
||||
# Environment variables to include in cache key
|
||||
#
|
||||
# Required: false
|
||||
# Default: ""
|
||||
```
|
||||
@@ -1,122 +0,0 @@
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
|
||||
# permissions:
|
||||
# - contents: read # Required for reading cache contents
|
||||
---
|
||||
name: Common Cache
|
||||
description: 'Standardized caching strategy for all actions'
|
||||
author: 'Ismo Vuorinen'
|
||||
|
||||
branding:
|
||||
icon: database
|
||||
color: gray-dark
|
||||
|
||||
inputs:
|
||||
type:
|
||||
description: 'Type of cache (npm, composer, go, pip, etc.)'
|
||||
required: true
|
||||
paths:
|
||||
description: 'Paths to cache (comma-separated)'
|
||||
required: true
|
||||
key-prefix:
|
||||
description: 'Custom prefix for cache key'
|
||||
required: false
|
||||
default: ''
|
||||
key-files:
|
||||
description: 'Files to hash for cache key (comma-separated)'
|
||||
required: false
|
||||
default: ''
|
||||
restore-keys:
|
||||
description: 'Fallback keys for cache restoration'
|
||||
required: false
|
||||
default: ''
|
||||
env-vars:
|
||||
description: 'Environment variables to include in cache key'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
outputs:
|
||||
cache-hit:
|
||||
description: 'Cache hit indicator'
|
||||
value: ${{ steps.cache.outputs.cache-hit }}
|
||||
cache-key:
|
||||
description: 'Generated cache key'
|
||||
value: ${{ steps.prepare.outputs.cache-key }}
|
||||
cache-paths:
|
||||
description: 'Resolved cache paths'
|
||||
value: ${{ steps.prepare.outputs.cache-paths }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- id: prepare
|
||||
shell: bash
|
||||
env:
|
||||
RUNNER_OS: ${{ runner.os }}
|
||||
CACHE_TYPE: ${{ inputs.type }}
|
||||
KEY_PREFIX: ${{ inputs.key-prefix }}
|
||||
KEY_FILES: ${{ inputs.key-files }}
|
||||
ENV_VARS: ${{ inputs.env-vars }}
|
||||
CACHE_PATHS: ${{ inputs.paths }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Generate standardized cache key components
|
||||
os_key="$RUNNER_OS"
|
||||
type_key="$CACHE_TYPE"
|
||||
prefix_key="$KEY_PREFIX"
|
||||
|
||||
# Process file hashes
|
||||
# Note: For simple glob patterns, hashFiles() function could be used directly
|
||||
# in the cache key. This manual approach is used to support comma-separated
|
||||
# file lists with complex cache key construction.
|
||||
files_hash=""
|
||||
if [ -n "$KEY_FILES" ]; then
|
||||
IFS=',' read -ra FILES <<< "$KEY_FILES"
|
||||
existing_files=()
|
||||
for file in "${FILES[@]}"; do
|
||||
# Trim whitespace
|
||||
file=$(echo "$file" | xargs)
|
||||
if [ -f "$file" ]; then
|
||||
existing_files+=("$file")
|
||||
fi
|
||||
done
|
||||
# Hash all files together for better performance
|
||||
if [ ${#existing_files[@]} -gt 0 ]; then
|
||||
files_hash=$(cat "${existing_files[@]}" | sha256sum | cut -d' ' -f1)
|
||||
fi
|
||||
fi
|
||||
|
||||
# Process environment variables
|
||||
env_hash=""
|
||||
if [ -n "$ENV_VARS" ]; then
|
||||
IFS=',' read -ra VARS <<< "$ENV_VARS"
|
||||
for var in "${VARS[@]}"; do
|
||||
if [ -n "${!var}" ]; then
|
||||
env_hash="${env_hash}-${var}-${!var}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Generate final cache key
|
||||
cache_key="${os_key}"
|
||||
[ -n "$prefix_key" ] && cache_key="${cache_key}-${prefix_key}"
|
||||
[ -n "$type_key" ] && cache_key="${cache_key}-${type_key}"
|
||||
[ -n "$files_hash" ] && cache_key="${cache_key}-${files_hash}"
|
||||
[ -n "$env_hash" ] && cache_key="${cache_key}-${env_hash}"
|
||||
|
||||
echo "cache-key=${cache_key}" >> $GITHUB_OUTPUT
|
||||
|
||||
# Process cache paths
|
||||
IFS=',' read -ra PATHS <<< "$CACHE_PATHS"
|
||||
cache_paths=""
|
||||
for path in "${PATHS[@]}"; do
|
||||
cache_paths="${cache_paths}${path}\n"
|
||||
done
|
||||
echo "cache-paths=${cache_paths}" >> $GITHUB_OUTPUT
|
||||
|
||||
- id: cache
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ${{ steps.prepare.outputs.cache-paths }}
|
||||
key: ${{ steps.prepare.outputs.cache-key }}
|
||||
restore-keys: ${{ inputs.restore-keys }}
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
# Validation rules for common-cache action
|
||||
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
||||
# Schema version: 1.0
|
||||
# Coverage: 50% (3/6 inputs)
|
||||
#
|
||||
# This file defines validation rules for the common-cache GitHub Action.
|
||||
# Rules are automatically applied by validate-inputs action when this
|
||||
# action is used.
|
||||
#
|
||||
|
||||
schema_version: '1.0'
|
||||
action: common-cache
|
||||
description: Standardized caching strategy for all actions
|
||||
generator_version: 1.0.0
|
||||
required_inputs:
|
||||
- paths
|
||||
- type
|
||||
optional_inputs:
|
||||
- env-vars
|
||||
- key-files
|
||||
- key-prefix
|
||||
- restore-keys
|
||||
conventions:
|
||||
key-files: file_path
|
||||
key-prefix: prefix
|
||||
paths: file_path
|
||||
overrides: {}
|
||||
statistics:
|
||||
total_inputs: 6
|
||||
validated_inputs: 3
|
||||
skipped_inputs: 0
|
||||
coverage_percentage: 50
|
||||
validation_coverage: 50
|
||||
auto_detected: true
|
||||
manual_review_required: true
|
||||
quality_indicators:
|
||||
has_required_inputs: true
|
||||
has_token_validation: false
|
||||
has_version_validation: false
|
||||
has_file_validation: true
|
||||
has_security_validation: false
|
||||
@@ -1,74 +0,0 @@
|
||||
"""Tests for common-cache custom validator.
|
||||
|
||||
Generated by generate-tests.py - Do not edit manually.
|
||||
"""
|
||||
# pylint: disable=invalid-name # Test file name matches action name
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add action directory to path to import custom validator
|
||||
action_path = Path(__file__).parent.parent.parent / "common-cache"
|
||||
sys.path.insert(0, str(action_path))
|
||||
|
||||
# pylint: disable=wrong-import-position
|
||||
from CustomValidator import CustomValidator
|
||||
|
||||
|
||||
class TestCustomCommonCacheValidator:
|
||||
"""Test cases for common-cache custom validator."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Set up test fixtures."""
|
||||
self.validator = CustomValidator("common-cache")
|
||||
|
||||
def teardown_method(self):
|
||||
"""Clean up after tests."""
|
||||
self.validator.clear_errors()
|
||||
|
||||
def test_validate_inputs_valid(self):
|
||||
"""Test validation with valid inputs."""
|
||||
# TODO: Add specific valid inputs for common-cache
|
||||
inputs = {}
|
||||
result = self.validator.validate_inputs(inputs)
|
||||
# Adjust assertion based on required inputs
|
||||
assert isinstance(result, bool)
|
||||
|
||||
def test_validate_inputs_invalid(self):
|
||||
"""Test validation with invalid inputs."""
|
||||
# TODO: Add specific invalid inputs for common-cache
|
||||
inputs = {"invalid_key": "invalid_value"}
|
||||
result = self.validator.validate_inputs(inputs)
|
||||
# Custom validators may have specific validation rules
|
||||
assert isinstance(result, bool)
|
||||
|
||||
def test_required_inputs(self):
|
||||
"""Test required inputs detection."""
|
||||
required = self.validator.get_required_inputs()
|
||||
assert isinstance(required, list)
|
||||
# TODO: Assert specific required inputs for common-cache
|
||||
|
||||
def test_validation_rules(self):
|
||||
"""Test validation rules."""
|
||||
rules = self.validator.get_validation_rules()
|
||||
assert isinstance(rules, dict)
|
||||
# TODO: Assert specific validation rules for common-cache
|
||||
|
||||
def test_github_expressions(self):
|
||||
"""Test GitHub expression handling."""
|
||||
inputs = {
|
||||
"test_input": "${{ github.token }}",
|
||||
}
|
||||
result = self.validator.validate_inputs(inputs)
|
||||
assert isinstance(result, bool)
|
||||
# GitHub expressions should generally be accepted
|
||||
|
||||
def test_error_propagation(self):
|
||||
"""Test error propagation from sub-validators."""
|
||||
# Custom validators often use sub-validators
|
||||
# Test that errors are properly propagated
|
||||
inputs = {"test": "value"}
|
||||
self.validator.validate_inputs(inputs)
|
||||
# Check error handling
|
||||
if self.validator.has_errors():
|
||||
assert len(self.validator.errors) > 0
|
||||
Reference in New Issue
Block a user