Integrate CSP hash update process into test framework
This commit is contained in:
parent
2b37907c27
commit
869b08ec0e
|
@ -0,0 +1,97 @@
|
|||
# Resume Site Tests
|
||||
|
||||
This directory contains tests for the resume site.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
- `unit/`: Unit tests for JavaScript files and other components
|
||||
- `integration/`: Integration tests that test components working together
|
||||
- `e2e/`: End-to-end tests that simulate user interactions
|
||||
- `*.test.js`, `*.spec.js`: Playwright test files
|
||||
- `lighthouse.js`: Lighthouse performance and accessibility tests
|
||||
- `serve.js`: Simple Node.js server for testing
|
||||
- `server.js`: Alternative server implementation
|
||||
- `pre-test-setup.sh`: Script to set up the test environment, including updating CSP hashes
|
||||
|
||||
## Running Tests
|
||||
|
||||
To run all tests:
|
||||
|
||||
```bash
|
||||
./run-all-tests.sh
|
||||
```
|
||||
|
||||
Or using npm:
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Run the pre-test setup script to update CSP hashes
|
||||
2. Check if the server is running
|
||||
3. Run all shell script tests
|
||||
4. Run Playwright tests if available
|
||||
5. Run Lighthouse tests
|
||||
|
||||
## Content Security Policy (CSP) Testing
|
||||
|
||||
The CSP hash update process is an important part of the testing framework. It ensures that:
|
||||
|
||||
1. All JavaScript and CSS files have integrity hashes
|
||||
2. All inline styles have proper CSP hashes
|
||||
3. The Caddyfile and HTML files have the correct CSP headers/meta tags
|
||||
|
||||
The `pre-test-setup.sh` script runs the `update-csp-hashes.sh` script to update all CSP hashes before running the tests. This ensures that any changes to the website are properly reflected in the CSP hashes.
|
||||
|
||||
The `csp-hash-test.sh` integration test checks if the CSP hash update process is working properly by verifying that:
|
||||
|
||||
- CSP headers are present in the response
|
||||
- CSP headers contain the required directives
|
||||
- JavaScript and CSS files have integrity attributes
|
||||
- HTML files have CSP meta tags
|
||||
|
||||
## Running Specific Tests
|
||||
|
||||
### JavaScript Tests
|
||||
|
||||
```bash
|
||||
npm run test:js
|
||||
```
|
||||
|
||||
### Lighthouse Tests
|
||||
|
||||
```bash
|
||||
npm run test:lighthouse
|
||||
```
|
||||
|
||||
### Starting the Test Server
|
||||
|
||||
```bash
|
||||
npm run serve
|
||||
```
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
### Shell Script Tests
|
||||
|
||||
Add new test scripts to the appropriate directory:
|
||||
- `unit/`: For unit tests
|
||||
- `integration/`: For integration tests
|
||||
- `e2e/`: For end-to-end tests
|
||||
|
||||
Shell script tests should:
|
||||
- Be executable bash scripts
|
||||
- Return exit code 0 for success, non-zero for failure
|
||||
- For integration and e2e tests, accept a base URL as the first argument
|
||||
|
||||
### Playwright Tests
|
||||
|
||||
Add new Playwright tests with the `.test.js` or `.spec.js` extension in the tests directory.
|
||||
|
||||
## Test Requirements
|
||||
|
||||
As per the project guidelines, all tests must:
|
||||
- Pass for both mobile and desktop viewports
|
||||
- Maintain Lighthouse scores: 100/100 for accessibility and SEO
|
||||
- Include meaningful assertions, not placeholders
|
|
@ -0,0 +1,92 @@
|
|||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# csp-hash-test.sh - Test the CSP hash update process
|
||||
# =====================================================================
|
||||
# This script checks if the CSP hash update process is working properly
|
||||
# =====================================================================
|
||||
|
||||
# Check if base URL is provided
|
||||
if [ -z "$1" ]; then
|
||||
BASE_URL="http://localhost:8080"
|
||||
else
|
||||
BASE_URL="$1"
|
||||
fi
|
||||
|
||||
echo "=== Testing CSP Hash Update Process ==="
|
||||
echo "Using base URL: $BASE_URL"
|
||||
|
||||
# Array to track failures
|
||||
FAILURES=0
|
||||
|
||||
# Check if the CSP headers are present
|
||||
echo "Checking if CSP headers are present..."
|
||||
RESPONSE=$(curl -s -I "$BASE_URL/")
|
||||
if echo "$RESPONSE" | grep -q "Content-Security-Policy"; then
|
||||
echo "✅ CSP header found in response"
|
||||
else
|
||||
echo "❌ CSP header not found in response"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
# Check if the CSP header contains the required directives
|
||||
echo "Checking if CSP header contains required directives..."
|
||||
CSP_HEADER=$(curl -s -I "$BASE_URL/" | grep -i "Content-Security-Policy" | sed 's/.*: //')
|
||||
|
||||
for directive in "default-src" "script-src" "style-src" "img-src" "font-src" "connect-src" "object-src" "frame-ancestors" "base-uri" "form-action"; do
|
||||
if echo "$CSP_HEADER" | grep -q "$directive"; then
|
||||
echo "✅ CSP header contains $directive directive"
|
||||
else
|
||||
echo "❌ CSP header does not contain $directive directive"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Check if JavaScript files have integrity attributes
|
||||
echo "Checking if JavaScript files have integrity attributes..."
|
||||
for js_file in "theme.js" "includes.js"; do
|
||||
HTML=$(curl -s "$BASE_URL/")
|
||||
if echo "$HTML" | grep -q "$js_file.*integrity"; then
|
||||
echo "✅ $js_file has integrity attribute"
|
||||
else
|
||||
echo "❌ $js_file does not have integrity attribute"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Check if CSS files have integrity attributes
|
||||
echo "Checking if CSS files have integrity attributes..."
|
||||
HTML=$(curl -s "$BASE_URL/")
|
||||
if echo "$HTML" | grep -q "styles.css.*integrity"; then
|
||||
echo "✅ styles.css has integrity attribute"
|
||||
else
|
||||
echo "❌ styles.css does not have integrity attribute"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
# Check if HTML files have CSP meta tags
|
||||
echo "Checking if HTML files have CSP meta tags..."
|
||||
HTML=$(curl -s "$BASE_URL/")
|
||||
if echo "$HTML" | grep -q '<meta http-equiv="Content-Security-Policy"'; then
|
||||
echo "✅ HTML file has CSP meta tag"
|
||||
else
|
||||
echo "❌ HTML file does not have CSP meta tag"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
# Check if the update-csp-hashes.sh script exists
|
||||
echo "Checking if update-csp-hashes.sh script exists..."
|
||||
if [ -f "$(pwd)/docker/resume/update-csp-hashes.sh" ]; then
|
||||
echo "✅ update-csp-hashes.sh script exists"
|
||||
else
|
||||
echo "❌ update-csp-hashes.sh script does not exist"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
# Check if any failures occurred
|
||||
if [ "$FAILURES" -eq 0 ]; then
|
||||
echo "=== All CSP Hash Tests Passed ==="
|
||||
exit 0
|
||||
else
|
||||
echo "=== CSP Hash Tests Failed: $FAILURES failures ==="
|
||||
exit 1
|
||||
fi
|
|
@ -0,0 +1,52 @@
|
|||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# test-csv-tool.sh - Test the CSV tool functionality
|
||||
# =====================================================================
|
||||
# This script checks if the CSV tool page loads without CSP errors
|
||||
# =====================================================================
|
||||
|
||||
# Check if base URL is provided
|
||||
if [ -z "$1" ]; then
|
||||
BASE_URL="http://localhost:8080"
|
||||
else
|
||||
BASE_URL="$1"
|
||||
fi
|
||||
|
||||
echo "=== Testing CSV Tool ==="
|
||||
echo "Using base URL: $BASE_URL"
|
||||
|
||||
# Create a test CSV file
|
||||
echo "Name,Age,City
|
||||
John,30,New York
|
||||
Jane,25,San Francisco
|
||||
Bob,40,Chicago" > /tmp/test.csv
|
||||
|
||||
# Check if the page loads properly
|
||||
echo "Checking if the CSV tool page loads properly..."
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/one-pager-tools/csv-tool.html")
|
||||
|
||||
if [ "$RESPONSE" -eq 200 ]; then
|
||||
echo "✅ CSV tool page loads successfully (HTTP $RESPONSE)"
|
||||
else
|
||||
echo "❌ CSV tool page failed to load (HTTP $RESPONSE)"
|
||||
rm -f /tmp/test.csv
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for CSP errors in the response headers
|
||||
echo "Checking for CSP errors in response headers..."
|
||||
CSP_HEADER=$(curl -s -I "$BASE_URL/one-pager-tools/csv-tool.html" | grep -i "Content-Security-Policy")
|
||||
|
||||
if [ -n "$CSP_HEADER" ]; then
|
||||
echo "✅ CSP header found in response"
|
||||
else
|
||||
echo "❌ CSP header not found in response"
|
||||
rm -f /tmp/test.csv
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean up
|
||||
rm -f /tmp/test.csv
|
||||
|
||||
echo "=== CSV Tool Test Completed Successfully ==="
|
||||
exit 0
|
|
@ -0,0 +1,122 @@
|
|||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# functional-test.sh - Test the main functionality of the website
|
||||
# =====================================================================
|
||||
# This script checks if the main features of the website are working
|
||||
# =====================================================================
|
||||
|
||||
# Check if base URL is provided
|
||||
if [ -z "$1" ]; then
|
||||
BASE_URL="http://localhost:8080"
|
||||
else
|
||||
BASE_URL="$1"
|
||||
fi
|
||||
|
||||
echo "=== Testing Website Functionality ==="
|
||||
echo "Using base URL: $BASE_URL"
|
||||
|
||||
# Array to track failures
|
||||
FAILURES=0
|
||||
|
||||
# Function to test a page and check for expected content
|
||||
test_page() {
|
||||
local url="$1"
|
||||
local name="$2"
|
||||
local expected_title="$3"
|
||||
local expected_content="$4"
|
||||
|
||||
echo "Testing $name page at $url"
|
||||
|
||||
# Check if the page loads
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$url")
|
||||
if [ "$RESPONSE" -eq 200 ]; then
|
||||
echo "✅ $name page loads successfully (HTTP $RESPONSE)"
|
||||
else
|
||||
echo "❌ $name page failed to load (HTTP $RESPONSE)"
|
||||
FAILURES=$((FAILURES+1))
|
||||
return
|
||||
fi
|
||||
|
||||
# Check page title
|
||||
TITLE=$(curl -s "$url" | grep -o "<title>.*</title>" | sed 's/<title>\(.*\)<\/title>/\1/')
|
||||
if [[ "$TITLE" == *"$expected_title"* ]]; then
|
||||
echo "✅ Page title matches: $TITLE"
|
||||
else
|
||||
echo "❌ Page title doesn't match. Expected: $expected_title, Got: $TITLE"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
# Check for expected content
|
||||
if [ -n "$expected_content" ]; then
|
||||
CONTENT=$(curl -s "$url")
|
||||
if echo "$CONTENT" | grep -q "$expected_content"; then
|
||||
echo "✅ Page contains expected content: $expected_content"
|
||||
else
|
||||
echo "❌ Page doesn't contain expected content: $expected_content"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "---"
|
||||
}
|
||||
|
||||
# Test main page
|
||||
test_page "$BASE_URL/" "Main" "Colin Knapp - Portfolio" "Colin Knapp"
|
||||
|
||||
# Test stories page
|
||||
test_page "$BASE_URL/stories/" "Stories" "Stories" "Case Studies"
|
||||
|
||||
# Test CSV tool
|
||||
test_page "$BASE_URL/one-pager-tools/csv-tool.html" "CSV Tool" "CSV Viewer" "Paste your CSV data here"
|
||||
|
||||
# Check for JavaScript files
|
||||
echo "Checking for required JavaScript files..."
|
||||
JS_FILES=("theme.js" "includes.js" "utils.js")
|
||||
for js_file in "${JS_FILES[@]}"; do
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/$js_file")
|
||||
if [ "$RESPONSE" -eq 200 ]; then
|
||||
echo "✅ $js_file loads successfully (HTTP $RESPONSE)"
|
||||
else
|
||||
echo "❌ $js_file failed to load (HTTP $RESPONSE)"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for CSS files
|
||||
echo "Checking for required CSS files..."
|
||||
CSS_FILES=("styles.css")
|
||||
for css_file in "${CSS_FILES[@]}"; do
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/$css_file")
|
||||
if [ "$RESPONSE" -eq 200 ]; then
|
||||
echo "✅ $css_file loads successfully (HTTP $RESPONSE)"
|
||||
else
|
||||
echo "❌ $css_file failed to load (HTTP $RESPONSE)"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for security headers
|
||||
echo "Checking for security headers..."
|
||||
HEADERS=$(curl -s -I "$BASE_URL/")
|
||||
if echo "$HEADERS" | grep -q "Content-Security-Policy"; then
|
||||
echo "✅ Content-Security-Policy header found"
|
||||
else
|
||||
echo "❌ Content-Security-Policy header not found"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
if echo "$HEADERS" | grep -q "X-Frame-Options"; then
|
||||
echo "✅ X-Frame-Options header found"
|
||||
else
|
||||
echo "❌ X-Frame-Options header not found"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
# Check if any failures occurred
|
||||
if [ "$FAILURES" -eq 0 ]; then
|
||||
echo "=== All Functionality Tests Passed ==="
|
||||
exit 0
|
||||
else
|
||||
echo "=== Functionality Tests Failed: $FAILURES failures ==="
|
||||
exit 1
|
||||
fi
|
|
@ -0,0 +1,108 @@
|
|||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# includes-test.sh - Test the includes functionality
|
||||
# =====================================================================
|
||||
# This script checks if the includes system is working properly
|
||||
# =====================================================================
|
||||
|
||||
# Check if base URL is provided
|
||||
if [ -z "$1" ]; then
|
||||
BASE_URL="http://localhost:8080"
|
||||
else
|
||||
BASE_URL="$1"
|
||||
fi
|
||||
|
||||
echo "=== Testing Includes Functionality ==="
|
||||
echo "Using base URL: $BASE_URL"
|
||||
|
||||
# Array to track failures
|
||||
FAILURES=0
|
||||
|
||||
# Test if includes.js exists and loads properly
|
||||
echo "Checking if includes.js exists and loads properly..."
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/includes.js")
|
||||
if [ "$RESPONSE" -eq 200 ]; then
|
||||
echo "✅ includes.js loads successfully (HTTP $RESPONSE)"
|
||||
|
||||
# Check includes.js content
|
||||
INCLUDES_JS=$(curl -s "$BASE_URL/includes.js")
|
||||
if echo "$INCLUDES_JS" | grep -q "includeHTML"; then
|
||||
echo "✅ includes.js contains includeHTML function"
|
||||
else
|
||||
echo "❌ includes.js doesn't contain includeHTML function"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
if echo "$INCLUDES_JS" | grep -q "DOMContentLoaded"; then
|
||||
echo "✅ includes.js contains DOMContentLoaded event listener"
|
||||
else
|
||||
echo "❌ includes.js doesn't contain DOMContentLoaded event listener"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
else
|
||||
echo "❌ includes.js failed to load (HTTP $RESPONSE)"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
# Check if the includes/header.html file exists
|
||||
echo "Checking includes/header.html file..."
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/includes/header.html")
|
||||
if [ "$RESPONSE" -eq 200 ] || [ "$RESPONSE" -eq 403 ]; then
|
||||
echo "✅ includes/header.html file exists (HTTP $RESPONSE)"
|
||||
else
|
||||
echo "❌ includes/header.html file doesn't exist (HTTP $RESPONSE)"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
# Check if pages with includes load correctly
|
||||
echo "Checking pages that use includes..."
|
||||
INCLUDE_PAGES=(
|
||||
"template-with-includes.html"
|
||||
"stories/story-with-includes.html"
|
||||
"one-pager-tools/tool-with-includes.html"
|
||||
)
|
||||
|
||||
for page in "${INCLUDE_PAGES[@]}"; do
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/$page")
|
||||
if [ "$RESPONSE" -eq 200 ]; then
|
||||
echo "✅ $page loads successfully (HTTP $RESPONSE)"
|
||||
|
||||
# Check if the page has include placeholders
|
||||
CONTENT=$(curl -s "$BASE_URL/$page")
|
||||
if echo "$CONTENT" | grep -q "id=\"header-include\""; then
|
||||
echo "✅ $page has header include placeholder"
|
||||
else
|
||||
echo "❌ $page doesn't have header include placeholder"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
if echo "$CONTENT" | grep -q "id=\"footer-include\""; then
|
||||
echo "✅ $page has footer include placeholder"
|
||||
else
|
||||
echo "❌ $page doesn't have footer include placeholder"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
# Check if includes.js is included in the page
|
||||
if echo "$CONTENT" | grep -q "includes.js"; then
|
||||
echo "✅ $page includes the includes.js script"
|
||||
else
|
||||
echo "❌ $page doesn't include the includes.js script"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
else
|
||||
echo "❌ $page failed to load (HTTP $RESPONSE)"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
echo "---"
|
||||
done
|
||||
|
||||
# Check if any failures occurred
|
||||
if [ "$FAILURES" -eq 0 ]; then
|
||||
echo "=== All Includes Tests Passed ==="
|
||||
exit 0
|
||||
else
|
||||
echo "=== Includes Tests Failed: $FAILURES failures ==="
|
||||
exit 1
|
||||
fi
|
|
@ -0,0 +1,63 @@
|
|||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# main-page-test.sh - Test the main page functionality
|
||||
# =====================================================================
|
||||
# This script checks if the main page loads correctly
|
||||
# =====================================================================
|
||||
|
||||
# Check if base URL is provided
|
||||
if [ -z "$1" ]; then
|
||||
BASE_URL="http://localhost:8080"
|
||||
else
|
||||
BASE_URL="$1"
|
||||
fi
|
||||
|
||||
echo "=== Testing Main Page ==="
|
||||
echo "Using base URL: $BASE_URL"
|
||||
|
||||
# Check if the main page loads properly
|
||||
echo "Checking if the main page loads properly..."
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/index.html")
|
||||
|
||||
if [ "$RESPONSE" -eq 200 ]; then
|
||||
echo "✅ Main page loads successfully (HTTP $RESPONSE)"
|
||||
else
|
||||
echo "❌ Main page failed to load (HTTP $RESPONSE)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for page title
|
||||
echo "Checking page title..."
|
||||
TITLE=$(curl -s "$BASE_URL/index.html" | grep -o "<title>.*</title>")
|
||||
|
||||
if [ -n "$TITLE" ]; then
|
||||
echo "✅ Page title found: $TITLE"
|
||||
else
|
||||
echo "❌ Page title not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for CSS loading
|
||||
echo "Checking if CSS loads properly..."
|
||||
CSS_LINK=$(curl -s "$BASE_URL/index.html" | grep -o '<link[^>]*href="[^"]*styles.css[^"]*"[^>]*>')
|
||||
|
||||
if [ -n "$CSS_LINK" ]; then
|
||||
echo "✅ CSS link found: $CSS_LINK"
|
||||
|
||||
# Check if the CSS file itself loads
|
||||
CSS_URL=$(echo "$CSS_LINK" | grep -o 'href="[^"]*"' | sed 's/href="//;s/"$//')
|
||||
CSS_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/$CSS_URL")
|
||||
|
||||
if [ "$CSS_RESPONSE" -eq 200 ]; then
|
||||
echo "✅ CSS file loads successfully (HTTP $CSS_RESPONSE)"
|
||||
else
|
||||
echo "❌ CSS file failed to load (HTTP $CSS_RESPONSE)"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ CSS link not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Main Page Test Completed Successfully ==="
|
||||
exit 0
|
|
@ -0,0 +1,76 @@
|
|||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# stories-test.sh - Test all story pages
|
||||
# =====================================================================
|
||||
# This script checks if all story pages load correctly
|
||||
# =====================================================================
|
||||
|
||||
# Check if base URL is provided
|
||||
if [ -z "$1" ]; then
|
||||
BASE_URL="http://localhost:8080"
|
||||
else
|
||||
BASE_URL="$1"
|
||||
fi
|
||||
|
||||
echo "=== Testing Story Pages ==="
|
||||
echo "Using base URL: $BASE_URL"
|
||||
|
||||
# Get the list of story pages from the stories index
|
||||
echo "Getting list of story pages..."
|
||||
STORY_LINKS=$(curl -s "$BASE_URL/stories/index.html" | grep -o 'href="[^"]*\.html"' | grep -v 'index.html' | sed 's/href="//;s/"$//')
|
||||
|
||||
if [ -z "$STORY_LINKS" ]; then
|
||||
echo "❌ No story links found in stories/index.html"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found story links: $STORY_LINKS"
|
||||
|
||||
# Test each story page
|
||||
FAILED=0
|
||||
for link in $STORY_LINKS; do
|
||||
# Make sure the link is properly formed
|
||||
if [[ "$link" != /* && "$link" != http* ]]; then
|
||||
# Relative link, add stories/ prefix if needed
|
||||
if [[ "$link" != stories/* ]]; then
|
||||
STORY_URL="$BASE_URL/stories/$link"
|
||||
else
|
||||
STORY_URL="$BASE_URL/$link"
|
||||
fi
|
||||
elif [[ "$link" == /* ]]; then
|
||||
# Absolute path
|
||||
STORY_URL="$BASE_URL$link"
|
||||
else
|
||||
# Full URL
|
||||
STORY_URL="$link"
|
||||
fi
|
||||
|
||||
echo "Testing story page: $STORY_URL"
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$STORY_URL")
|
||||
|
||||
if [ "$RESPONSE" -eq 200 ]; then
|
||||
echo "✅ Story page loads successfully (HTTP $RESPONSE)"
|
||||
|
||||
# Check for required elements
|
||||
CONTENT=$(curl -s "$STORY_URL")
|
||||
if echo "$CONTENT" | grep -q "<h1>" && echo "$CONTENT" | grep -q "<p>"; then
|
||||
echo "✅ Story page has required elements (h1 and p tags)"
|
||||
else
|
||||
echo "❌ Story page is missing required elements"
|
||||
FAILED=1
|
||||
fi
|
||||
else
|
||||
echo "❌ Story page failed to load (HTTP $RESPONSE)"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
echo "---"
|
||||
done
|
||||
|
||||
if [ "$FAILED" -eq 0 ]; then
|
||||
echo "=== All Story Pages Test Completed Successfully ==="
|
||||
exit 0
|
||||
else
|
||||
echo "=== Story Pages Test Failed ==="
|
||||
exit 1
|
||||
fi
|
|
@ -0,0 +1,88 @@
|
|||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# theme-test.sh - Test the theme functionality
|
||||
# =====================================================================
|
||||
# This script checks if the theme system is working properly
|
||||
# =====================================================================
|
||||
|
||||
# Check if base URL is provided
|
||||
if [ -z "$1" ]; then
|
||||
BASE_URL="http://localhost:8080"
|
||||
else
|
||||
BASE_URL="$1"
|
||||
fi
|
||||
|
||||
echo "=== Testing Theme Functionality ==="
|
||||
echo "Using base URL: $BASE_URL"
|
||||
|
||||
# Array to track failures
|
||||
FAILURES=0
|
||||
|
||||
# Test if theme.js exists and loads properly
|
||||
echo "Checking if theme.js exists and loads properly..."
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/theme.js")
|
||||
if [ "$RESPONSE" -eq 200 ]; then
|
||||
echo "✅ theme.js loads successfully (HTTP $RESPONSE)"
|
||||
|
||||
# Check theme.js content
|
||||
THEME_JS=$(curl -s "$BASE_URL/theme.js")
|
||||
if echo "$THEME_JS" | grep -q "DOMContentLoaded"; then
|
||||
echo "✅ theme.js contains DOMContentLoaded event listener"
|
||||
else
|
||||
echo "❌ theme.js doesn't contain DOMContentLoaded event listener"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
if echo "$THEME_JS" | grep -q "themeToggle"; then
|
||||
echo "✅ theme.js contains themeToggle functionality"
|
||||
else
|
||||
echo "❌ theme.js doesn't contain themeToggle functionality"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
if echo "$THEME_JS" | grep -q "localStorage"; then
|
||||
echo "✅ theme.js uses localStorage for theme persistence"
|
||||
else
|
||||
echo "❌ theme.js doesn't use localStorage for theme persistence"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
else
|
||||
echo "❌ theme.js failed to load (HTTP $RESPONSE)"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
# Check if CSS has theme-related styles
|
||||
echo "Checking if styles.css has theme-related styles..."
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/styles.css")
|
||||
if [ "$RESPONSE" -eq 200 ]; then
|
||||
echo "✅ styles.css loads successfully (HTTP $RESPONSE)"
|
||||
|
||||
# Check styles.css content for theme-related styles
|
||||
STYLES_CSS=$(curl -s "$BASE_URL/styles.css")
|
||||
if echo "$STYLES_CSS" | grep -q "data-theme"; then
|
||||
echo "✅ styles.css contains data-theme attribute selectors"
|
||||
else
|
||||
echo "❌ styles.css doesn't contain data-theme attribute selectors"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
# Check for dark mode styles
|
||||
if echo "$STYLES_CSS" | grep -q "dark"; then
|
||||
echo "✅ styles.css contains dark mode styles"
|
||||
else
|
||||
echo "❌ styles.css doesn't contain dark mode styles"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
else
|
||||
echo "❌ styles.css failed to load (HTTP $RESPONSE)"
|
||||
FAILURES=$((FAILURES+1))
|
||||
fi
|
||||
|
||||
# Check if any failures occurred
|
||||
if [ "$FAILURES" -eq 0 ]; then
|
||||
echo "=== All Theme Tests Passed ==="
|
||||
exit 0
|
||||
else
|
||||
echo "=== Theme Tests Failed: $FAILURES failures ==="
|
||||
exit 1
|
||||
fi
|
|
@ -0,0 +1,35 @@
|
|||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# pre-test-setup.sh - Setup for tests
|
||||
# =====================================================================
|
||||
# This script sets up the environment for testing
|
||||
# =====================================================================
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
TESTS_DIR="$(dirname "$0")"
|
||||
RESUME_DIR="$(pwd)/docker/resume"
|
||||
|
||||
echo "=== Setting Up Test Environment ==="
|
||||
|
||||
# Check if we're in the correct directory
|
||||
if [ ! -d "$RESUME_DIR" ]; then
|
||||
echo "Error: Could not find the resume directory at $RESUME_DIR"
|
||||
echo "Make sure you're running this script from the project root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run the CSP hash update script
|
||||
echo "Running CSP hash update script..."
|
||||
cd "$RESUME_DIR"
|
||||
if [ -f "./update-csp-hashes.sh" ]; then
|
||||
./update-csp-hashes.sh
|
||||
else
|
||||
echo "Error: Could not find update-csp-hashes.sh script"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Return to the original directory
|
||||
cd - > /dev/null
|
||||
|
||||
echo "=== Test Environment Setup Complete ==="
|
|
@ -0,0 +1,112 @@
|
|||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# run-all-tests.sh - Run all tests against an existing server
|
||||
# =====================================================================
|
||||
# This script runs all tests against an existing server
|
||||
# =====================================================================
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
# Define constants
|
||||
BASE_URL="http://localhost:8080"
|
||||
TESTS_DIR="$(dirname "$0")"
|
||||
|
||||
# Run pre-test setup
|
||||
echo "Running pre-test setup..."
|
||||
if [ -f "$TESTS_DIR/pre-test-setup.sh" ]; then
|
||||
"$TESTS_DIR/pre-test-setup.sh"
|
||||
else
|
||||
echo "Warning: pre-test-setup.sh not found, skipping setup"
|
||||
fi
|
||||
|
||||
# Check if the server is running
|
||||
echo "Checking if server is running at $BASE_URL..."
|
||||
if ! curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/" | grep -q "200"; then
|
||||
echo "Server is not running at $BASE_URL. Please start it using:"
|
||||
echo "cd docker/resume && ./caddy.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Server is running at $BASE_URL"
|
||||
|
||||
# Run shell script tests
|
||||
run_shell_tests() {
|
||||
echo "Running shell script tests..."
|
||||
|
||||
# Run unit tests
|
||||
echo "Running unit tests..."
|
||||
if [ -d "$TESTS_DIR/unit" ] && [ "$(ls -A "$TESTS_DIR/unit")" ]; then
|
||||
for test in "$TESTS_DIR/unit"/*.sh; do
|
||||
if [ -f "$test" ] && [ -x "$test" ]; then
|
||||
echo "Running unit test: $(basename "$test")"
|
||||
"$test" || echo "FAILED: $(basename "$test")"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "No unit tests found."
|
||||
fi
|
||||
|
||||
# Run integration tests
|
||||
echo "Running integration tests..."
|
||||
if [ -d "$TESTS_DIR/integration" ] && [ "$(ls -A "$TESTS_DIR/integration")" ]; then
|
||||
for test in "$TESTS_DIR/integration"/*.sh; do
|
||||
if [ -f "$test" ] && [ -x "$test" ]; then
|
||||
echo "Running integration test: $(basename "$test")"
|
||||
"$test" "$BASE_URL" || echo "FAILED: $(basename "$test")"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for subdirectories
|
||||
for dir in "$TESTS_DIR/integration"/*/; do
|
||||
if [ -d "$dir" ]; then
|
||||
for test in "$dir"*.sh; do
|
||||
if [ -f "$test" ] && [ -x "$test" ]; then
|
||||
echo "Running integration test: $(basename "$test")"
|
||||
"$test" "$BASE_URL" || echo "FAILED: $(basename "$test")"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "No integration tests found."
|
||||
fi
|
||||
|
||||
# Run e2e tests
|
||||
echo "Running e2e tests..."
|
||||
if [ -d "$TESTS_DIR/e2e" ] && [ "$(ls -A "$TESTS_DIR/e2e")" ]; then
|
||||
for test in "$TESTS_DIR/e2e"/*.sh; do
|
||||
if [ -f "$test" ] && [ -x "$test" ]; then
|
||||
echo "Running e2e test: $(basename "$test")"
|
||||
"$test" "$BASE_URL" || echo "FAILED: $(basename "$test")"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "No e2e tests found."
|
||||
fi
|
||||
}
|
||||
|
||||
# Run JavaScript tests
|
||||
run_js_tests() {
|
||||
echo "Running JavaScript tests..."
|
||||
|
||||
# Check if Playwright is installed
|
||||
if command -v npx &> /dev/null && npx playwright --version &> /dev/null; then
|
||||
# Run Playwright tests
|
||||
echo "Running Playwright tests..."
|
||||
npx playwright test || echo "FAILED: Playwright tests"
|
||||
else
|
||||
echo "Playwright not found, skipping Playwright tests."
|
||||
fi
|
||||
|
||||
# Run Lighthouse tests if available
|
||||
if [ -f "$TESTS_DIR/lighthouse.js" ]; then
|
||||
echo "Running Lighthouse tests..."
|
||||
node "$TESTS_DIR/lighthouse.js" "$BASE_URL" || echo "FAILED: Lighthouse tests"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
echo "=== Starting Test Suite ==="
|
||||
run_shell_tests
|
||||
run_js_tests
|
||||
echo "=== Test Suite Completed ==="
|
|
@ -0,0 +1,192 @@
|
|||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# run-tests.sh - Main test runner for the resume site
|
||||
# =====================================================================
|
||||
# This script starts a local Caddy server and runs all tests against it
|
||||
# =====================================================================
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
# Define constants
|
||||
TEST_PORT=8080
|
||||
CADDY_DIR="docker/resume"
|
||||
CADDY_FILE="Caddyfile.local"
|
||||
TESTS_DIR="$(dirname "$0")"
|
||||
LOG_FILE="$TESTS_DIR/test-run.log"
|
||||
|
||||
# Function to clean up processes on exit
|
||||
cleanup() {
|
||||
echo "Cleaning up..."
|
||||
# Find and kill any Caddy processes we started
|
||||
if [ -f "$TESTS_DIR/.caddy.pid" ]; then
|
||||
CADDY_PID=$(cat "$TESTS_DIR/.caddy.pid")
|
||||
if ps -p $CADDY_PID > /dev/null; then
|
||||
echo "Stopping Caddy server (PID: $CADDY_PID)"
|
||||
kill $CADDY_PID
|
||||
fi
|
||||
rm "$TESTS_DIR/.caddy.pid"
|
||||
fi
|
||||
|
||||
echo "Cleanup complete"
|
||||
}
|
||||
|
||||
# Register the cleanup function to run on exit
|
||||
trap cleanup EXIT
|
||||
|
||||
# Start the Caddy server
|
||||
start_caddy() {
|
||||
echo "Starting Caddy server on port $TEST_PORT..."
|
||||
|
||||
# Navigate to the Caddy directory
|
||||
cd "$CADDY_DIR"
|
||||
|
||||
# Check if Caddyfile.local exists, if not, create it from Caddyfile
|
||||
if [ ! -f "$CADDY_FILE" ]; then
|
||||
echo "Creating $CADDY_FILE from Caddyfile..."
|
||||
cp Caddyfile "$CADDY_FILE"
|
||||
# Modify the Caddyfile.local to use the test port
|
||||
sed -i '' "s/:80/:$TEST_PORT/g" "$CADDY_FILE"
|
||||
fi
|
||||
|
||||
# Start Caddy in the background using the local config
|
||||
echo "Running: caddy run --config $CADDY_FILE"
|
||||
mkdir -p $(dirname "$LOG_FILE") && caddy run --config "$CADDY_FILE" > "$LOG_FILE" 2>&1 &
|
||||
CADDY_PID=$!
|
||||
mkdir -p "$TESTS_DIR" && echo $CADDY_PID > "$TESTS_DIR/.caddy.pid"
|
||||
|
||||
# Return to the original directory
|
||||
cd - > /dev/null
|
||||
|
||||
# Wait for Caddy to start
|
||||
echo "Waiting for Caddy to start..."
|
||||
sleep 2
|
||||
|
||||
# Check if Caddy is running
|
||||
if ! ps -p $CADDY_PID > /dev/null; then
|
||||
echo "Failed to start Caddy server. Check $LOG_FILE for details."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Caddy server started with PID: $CADDY_PID"
|
||||
|
||||
# Wait a bit more to ensure Caddy is fully initialized
|
||||
sleep 3
|
||||
}
|
||||
|
||||
# Run Node.js server for tests that require it
|
||||
start_node_server() {
|
||||
if [ -f "$TESTS_DIR/serve.js" ]; then
|
||||
echo "Starting Node.js server for tests..."
|
||||
node "$TESTS_DIR/serve.js" > "$TESTS_DIR/node-server.log" 2>&1 &
|
||||
NODE_SERVER_PID=$!
|
||||
echo $NODE_SERVER_PID > "$TESTS_DIR/.node-server.pid"
|
||||
|
||||
# Wait for Node.js server to start
|
||||
sleep 2
|
||||
|
||||
# Check if Node.js server is running
|
||||
if ! ps -p $NODE_SERVER_PID > /dev/null; then
|
||||
echo "Failed to start Node.js server. Check $TESTS_DIR/node-server.log for details."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Node.js server started with PID: $NODE_SERVER_PID"
|
||||
fi
|
||||
}
|
||||
|
||||
# Clean up Node.js server
|
||||
cleanup_node_server() {
|
||||
if [ -f "$TESTS_DIR/.node-server.pid" ]; then
|
||||
NODE_SERVER_PID=$(cat "$TESTS_DIR/.node-server.pid")
|
||||
if ps -p $NODE_SERVER_PID > /dev/null; then
|
||||
echo "Stopping Node.js server (PID: $NODE_SERVER_PID)"
|
||||
kill $NODE_SERVER_PID
|
||||
fi
|
||||
rm "$TESTS_DIR/.node-server.pid"
|
||||
fi
|
||||
}
|
||||
|
||||
# Run shell script tests
|
||||
run_shell_tests() {
|
||||
echo "Running shell script tests..."
|
||||
|
||||
# Run unit tests
|
||||
echo "Running unit tests..."
|
||||
if [ -d "$TESTS_DIR/unit" ] && [ "$(ls -A "$TESTS_DIR/unit")" ]; then
|
||||
for test in "$TESTS_DIR/unit"/*.sh; do
|
||||
if [ -f "$test" ] && [ -x "$test" ]; then
|
||||
echo "Running unit test: $(basename "$test")"
|
||||
"$test" || echo "FAILED: $(basename "$test")"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "No unit tests found."
|
||||
fi
|
||||
|
||||
# Run integration tests
|
||||
echo "Running integration tests..."
|
||||
if [ -d "$TESTS_DIR/integration" ] && [ "$(ls -A "$TESTS_DIR/integration")" ]; then
|
||||
for test in "$TESTS_DIR/integration"/*.sh; do
|
||||
if [ -f "$test" ] && [ -x "$test" ]; then
|
||||
echo "Running integration test: $(basename "$test")"
|
||||
"$test" "http://localhost:$TEST_PORT" || echo "FAILED: $(basename "$test")"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for subdirectories
|
||||
for dir in "$TESTS_DIR/integration"/*/; do
|
||||
if [ -d "$dir" ]; then
|
||||
for test in "$dir"*.sh; do
|
||||
if [ -f "$test" ] && [ -x "$test" ]; then
|
||||
echo "Running integration test: $(basename "$test")"
|
||||
"$test" "http://localhost:$TEST_PORT" || echo "FAILED: $(basename "$test")"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "No integration tests found."
|
||||
fi
|
||||
|
||||
# Run e2e tests
|
||||
echo "Running e2e tests..."
|
||||
if [ -d "$TESTS_DIR/e2e" ] && [ "$(ls -A "$TESTS_DIR/e2e")" ]; then
|
||||
for test in "$TESTS_DIR/e2e"/*.sh; do
|
||||
if [ -f "$test" ] && [ -x "$test" ]; then
|
||||
echo "Running e2e test: $(basename "$test")"
|
||||
"$test" "http://localhost:$TEST_PORT" || echo "FAILED: $(basename "$test")"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "No e2e tests found."
|
||||
fi
|
||||
}
|
||||
|
||||
# Run JavaScript tests
|
||||
run_js_tests() {
|
||||
echo "Running JavaScript tests..."
|
||||
|
||||
# Check if Playwright is installed
|
||||
if command -v npx &> /dev/null && npx playwright --version &> /dev/null; then
|
||||
# Run Playwright tests
|
||||
echo "Running Playwright tests..."
|
||||
npx playwright test || echo "FAILED: Playwright tests"
|
||||
else
|
||||
echo "Playwright not found, skipping Playwright tests."
|
||||
fi
|
||||
|
||||
# Run Lighthouse tests if available
|
||||
if [ -f "$TESTS_DIR/lighthouse.js" ]; then
|
||||
echo "Running Lighthouse tests..."
|
||||
node "$TESTS_DIR/lighthouse.js" "http://localhost:$TEST_PORT" || echo "FAILED: Lighthouse tests"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
echo "=== Starting Test Suite ==="
|
||||
start_caddy
|
||||
start_node_server
|
||||
run_shell_tests
|
||||
run_js_tests
|
||||
cleanup_node_server
|
||||
echo "=== Test Suite Completed ==="
|
|
@ -0,0 +1,48 @@
|
|||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# includes-test.sh - Test the includes.js functionality
|
||||
# =====================================================================
|
||||
# This script checks if the includes.js file is valid JavaScript
|
||||
# =====================================================================
|
||||
|
||||
echo "=== Testing includes.js ==="
|
||||
|
||||
# Path to the includes.js file
|
||||
INCLUDES_JS="docker/resume/includes.js"
|
||||
|
||||
# Check if the file exists
|
||||
if [ ! -f "$INCLUDES_JS" ]; then
|
||||
echo "❌ File not found: $INCLUDES_JS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ File exists: $INCLUDES_JS"
|
||||
|
||||
# Check if the file is valid JavaScript using node
|
||||
if command -v node &> /dev/null; then
|
||||
echo "Checking if the file is valid JavaScript..."
|
||||
if node --check "$INCLUDES_JS" &> /dev/null; then
|
||||
echo "✅ File is valid JavaScript"
|
||||
else
|
||||
echo "❌ File contains JavaScript syntax errors"
|
||||
node --check "$INCLUDES_JS"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "⚠️ Node.js not found, skipping JavaScript syntax check"
|
||||
fi
|
||||
|
||||
# Check for required functions
|
||||
echo "Checking for required functions..."
|
||||
REQUIRED_FUNCTIONS=("includeHTML")
|
||||
for func in "${REQUIRED_FUNCTIONS[@]}"; do
|
||||
if grep -q "function $func" "$INCLUDES_JS"; then
|
||||
echo "✅ Required function found: $func"
|
||||
else
|
||||
echo "❌ Required function not found: $func"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "=== includes.js Test Completed Successfully ==="
|
||||
exit 0
|
|
@ -0,0 +1,45 @@
|
|||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# theme-test.sh - Test the theme.js functionality
|
||||
# =====================================================================
|
||||
# This script checks if the theme.js file is valid JavaScript
|
||||
# =====================================================================
|
||||
|
||||
echo "=== Testing theme.js ==="
|
||||
|
||||
# Path to the theme.js file
|
||||
THEME_JS="docker/resume/theme.js"
|
||||
|
||||
# Check if the file exists
|
||||
if [ ! -f "$THEME_JS" ]; then
|
||||
echo "❌ File not found: $THEME_JS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ File exists: $THEME_JS"
|
||||
|
||||
# Check if the file is valid JavaScript using node
|
||||
if command -v node &> /dev/null; then
|
||||
echo "Checking if the file is valid JavaScript..."
|
||||
if node --check "$THEME_JS" &> /dev/null; then
|
||||
echo "✅ File is valid JavaScript"
|
||||
else
|
||||
echo "❌ File contains JavaScript syntax errors"
|
||||
node --check "$THEME_JS"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "⚠️ Node.js not found, skipping JavaScript syntax check"
|
||||
fi
|
||||
|
||||
# Check for theme-related functionality
|
||||
echo "Checking for theme-related functionality..."
|
||||
if grep -q "dark" "$THEME_JS" || grep -q "light" "$THEME_JS" || grep -q "theme" "$THEME_JS"; then
|
||||
echo "✅ Theme-related functionality found"
|
||||
else
|
||||
echo "❌ No theme-related functionality found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== theme.js Test Completed Successfully ==="
|
||||
exit 0
|
Loading…
Reference in New Issue