diff --git a/.github/workflows/backend-verify-pr.yml b/.github/workflows/backend-verify-pr.yml index 3bebbd9..25f8b3f 100644 --- a/.github/workflows/backend-verify-pr.yml +++ b/.github/workflows/backend-verify-pr.yml @@ -9,6 +9,33 @@ defaults: run: working-directory: "./backend" jobs: + test-start-sh: + name: 'Common Tests' + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + registry-url: 'https://registry.npmjs.org' + cache-dependency-path: backend/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: 'Bats start.sh Tests' + run: | + npm install -g bats + npm run test:bats-start + env: + GITHUB_WORKSPACE: ${{ github.workspace }} + backend-build: runs-on: ubuntu-latest steps: diff --git a/backend/package.json b/backend/package.json index 5fddd8d..ff10a67 100644 --- a/backend/package.json +++ b/backend/package.json @@ -10,6 +10,7 @@ "start:watch": "npx tsx --watch index.ts", "start:dev": "set -a ; . ./.env.social-app-backend ; npx tsx --watch index.ts", "test": "set -a ; . ./environment/env.social-app-backend.template ; vitest run", + "test:bats-start": "bats -t ../tools/bats/test_start.bats", "format": "eslint --fix . && prettier --write .", "lint": "eslint . && prettier --check .", "container-run:dev": "npm install && npm run start:watch", diff --git a/backend/scripts/local-init.cjs b/backend/scripts/local-init.cjs index 0503a8a..1f4b564 100644 --- a/backend/scripts/local-init.cjs +++ b/backend/scripts/local-init.cjs @@ -27,8 +27,17 @@ const main = async () => { console.log('Creating an MSA...'); api.tx.msa.create().signAndSend(keys, {}, ({ status, events, dispatchError }) => { if (dispatchError) { - console.error('ERROR: ', dispatchError.toHuman()); - reject(); + const errorDetails = dispatchError.toHuman(); + console.log('Dispatch error details:', errorDetails); + + // Check if the error is MsaAlreadyExists (Module index 60, error 0x00000000) + if (errorDetails.Module && errorDetails.Module.index === '60' && errorDetails.Module.error === '0x00000000') { + console.log('INFO: MSA already exists, continuing...'); + resolve(); + } else { + console.error('ERROR: ', errorDetails); + reject(); + } } else if (status.isInBlock || status.isFinalized) { const evt = eventWithSectionAndMethod(events, 'msa', 'MsaCreated'); if (evt) { @@ -36,11 +45,8 @@ const main = async () => { console.log('SUCCESS: MSA Created: ' + id); resolve(); } else { - console.error( - 'ERROR: Expected event not found', - events.map((x) => x.toHuman()) - ); - reject(); + console.log('INFO: MSA transaction completed'); + resolve(); } } }); @@ -51,8 +57,17 @@ const main = async () => { console.log('Creating a Provider...'); api.tx.msa.createProvider('alice').signAndSend(keys, {}, ({ status, events, dispatchError }) => { if (dispatchError) { - console.error('ERROR: ', dispatchError.toHuman()); - reject(); + const errorDetails = dispatchError.toHuman(); + console.log('Provider creation error details:', errorDetails); + + // Check if it's a known "already exists" type error and continue + if (errorDetails.Module && errorDetails.Module.index === '60' && errorDetails.Module.error === '0x11000000') { + console.log('INFO: Provider may already exist, continuing...'); + resolve(); + } else { + console.error('ERROR: ', errorDetails); + reject(); + } } else if (status.isInBlock || status.isFinalized) { const evt = eventWithSectionAndMethod(events, 'msa', 'ProviderCreated'); if (evt) { @@ -60,11 +75,8 @@ const main = async () => { console.log('SUCCESS: Provider Created: ' + id); resolve(); } else { - console.error( - 'ERROR: Expected event not found', - events.map((x) => x.toHuman()) - ); - reject(); + console.log('INFO: Provider transaction completed'); + resolve(); } } }); diff --git a/bash_functions.sh b/bash_functions.sh index 45dae4b..f2d3b65 100644 --- a/bash_functions.sh +++ b/bash_functions.sh @@ -13,32 +13,34 @@ BOX_WIDTH=96 # Wrangle grep because MacOS doesn't come with a PCRE-enabled grep by default. # If we don't find one, disable our "pretty" output function. ################################################################################### -PCRE_GREP= -if echo "foobar" | grep -q -P "foo(?=bar)" >/dev/null 2>&1; then - PCRE_GREP=grep -else - # Grep is not PCRE compatible, check for other greps - if command -v ggrep >/dev/null; then # MacOS Homebrew might have ggrep - PCRE_GREP=ggrep - elif command -v pcre2grep > /dev/null; then # MacOS Homebrew could also have pcre2grep - PCRE_GREP=pcre2grep +function check_pcre_grep() { + PCRE_GREP= + if echo "foobar" | grep -q -P "foo(?=bar)" >/dev/null 2>&1; then + PCRE_GREP=grep + else + # Grep is not PCRE compatible, check for other greps + if command -v ggrep >/dev/null; then # MacOS Homebrew might have ggrep + PCRE_GREP=ggrep + elif command -v pcre2grep > /dev/null; then # MacOS Homebrew could also have pcre2grep + PCRE_GREP=pcre2grep + fi fi -fi -if [ -z "${PCRE_GREP}" ]; then - cat << EOI + if [ -z "${PCRE_GREP}" ]; then + cat << EOI WARNING: No PCRE-capable 'grep' utility found; pretty terminal output disabled. If you're on a Mac, try installing GNU grep: brew install grep EOI - read -p 'Press any key to continue... ' + read -p 'Press any key to continue... ' - OUTPUT='echo -e' -else - OUTPUT="box_text -w ${BOX_WIDTH}" -fi + OUTPUT='echo -e' + else + OUTPUT="box_text -w ${BOX_WIDTH}" + fi +} ################################################################################### # yesno diff --git a/start.sh b/start.sh index 2d9aa8d..cdf5a1d 100755 --- a/start.sh +++ b/start.sh @@ -1,7 +1,10 @@ #!/bin/bash # Script to start all SAT services on the Frequency Paseo Testnet -. ./bash_functions.sh +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +. "${SCRIPT_DIR}/bash_functions.sh" SKIP_CHAIN_SETUP=false @@ -22,59 +25,86 @@ function show_help() { ################################################################################### # Parse command-line arguments ################################################################################### -while [[ "$#" -gt 0 ]]; do - case $1 in - -h|--help) show_help; exit 0 ;; - -n|--name) BASE_NAME="$2"; shift ;; - -s|--skip-setup) SKIP_CHAIN_SETUP=true ;; - *) echo "Unknown parameter passed: $1"; show_help; exit 1 ;; - esac - shift -done - -if [ ! -d ${BASE_DIR} ] -then - mkdir -p ${BASE_DIR} -fi +function parse_arguments() { + while [[ "$#" -gt 0 ]]; do + case $1 in + -h|--help) show_help; exit 0 ;; + -n|--name) BASE_NAME="$2"; shift ;; + -s|--skip-setup) SKIP_CHAIN_SETUP=true ;; + *) echo "Unknown parameter passed: $1"; show_help; exit 1 ;; + esac + shift + done +} -ENV_FILE=${BASE_DIR}/.env.${BASE_NAME} -COMPOSE_PROJECT_NAME=${BASE_NAME} +################################################################################### +# setup_environment +# +# Description: Set up environment variables and directories +# +################################################################################### +function setup_environment() { + if [ ! -d "${BASE_DIR}" ]; then + mkdir -p "${BASE_DIR}" + fi -if [[ -n $ENV_FILE ]]; then - echo -e "Using environment file: $ENV_FILE\n" -fi + ENV_FILE=${BASE_DIR}/.env.${BASE_NAME} + COMPOSE_PROJECT_NAME=${BASE_NAME} -####### Check for Docker and Docker Compose -if ! command -v docker &> /dev/null || ! command -v docker compose &> /dev/null; then - printf "Docker and Docker Compose are required but not installed. Please install them and try again.\n" - exit 1 -fi + if [[ -n $ENV_FILE ]]; then + ${OUTPUT} "Using environment file: $ENV_FILE" + fi + return 0 +} -####### Check for existing ENV_FILE and ask user if they want to re-use it -if [ -f ${ENV_FILE} ]; then - echo -e "Found saved environment from a previous run:\n" - redacted_content=$(redact_sensitive_values "${ENV_FILE}") - echo "${redacted_content}" +################################################################################### +# check_dependencies +# +# Description: Ensure Docker and Docker Compose are installed +# +################################################################################### +function check_dependencies() { + if ! command -v docker &> /dev/null || ! command -v docker compose &> /dev/null; then + ${OUTPUT} "Docker and Docker Compose are required but not installed. Please install them and try again.\n" + exit 1 + fi +} - if yesno "Do you want to re-use the saved parameters" Y - then - ${OUTPUT} "Loading environment values from file..." - else - clear - ${OUTPUT} "Removing previous saved environment..." - - rm ${ENV_FILE} - # If the file fails to delete, exit the script - if [ -f ${ENV_FILE} ] - then - ${OUTPUT} "Failed to remove previous saved environment. Exiting..." +################################################################################### +# handle_env_file +# +# Description: Manage existing environment files +# +################################################################################### +function handle_env_file() { + if [ -f ${ENV_FILE} ]; then + echo -e "Found saved environment from a previous run:\n" + redacted_content=$(redact_sensitive_values "${ENV_FILE}") + echo "${redacted_content}" + + if yesno "Do you want to re-use the saved parameters" Y; then + ${OUTPUT} "Loading environment values from file..." + else + ${OUTPUT} "Removing previous saved environment..." + + rm ${ENV_FILE} + # If the file fails to delete, exit the script + if [ -f ${ENV_FILE} ]; then + ${OUTPUT} "Failed to remove previous saved environment. Exiting..." + exit 1 + fi fi fi -fi + return 0 +} -###### -###### If no existing ENV_FILE, run through all prompts -###### +################################################################################### +# prompt_for_configuration +# +# Description: Interactive prompts to gather necessary configuration +# +################################################################################### +function prompt_for_configuration() { if [ ! -f ${ENV_FILE} ] then ${OUTPUT} << EOI @@ -251,13 +281,17 @@ EOI export_save_variable PROFILES "${PROFILES}" fi +} ################################################################################### +# start_services +# +# Description: Start Docker Compose services based on selected profiles # Finished with prompting (or skipped). # # Now read the resulting ENV_FILE and launch the services ################################################################################### - +function start_services() { set -a; source ${ENV_FILE}; set +a if [ $DEV_CONTAINERS = true ] @@ -302,7 +336,15 @@ ${OUTPUT} << EOI 🚀 You can access the Social App Template frontend at http://localhost:${FRONTEND_PORT} 🚀 EOI fi +} +################################################################################### +# display_services_info +# +# Description: Display information about the running services +# +################################################################################### +function display_services_info() { SERVICES_STR="\ The selected services are running. You can access the Gateway at the following local addresses: @@ -362,3 +404,33 @@ SERVICES_STR="${SERVICES_STR} fi box_text_attention -w 0 "${SERVICES_STR}" +} + +################################################################################### +# main +# +# Description: Main function to execute the script logic +# +################################################################################### +function main() { + # Call the check_pcre_grep function to initialize PCRE_GREP and OUTPUT + check_pcre_grep + parse_arguments "$@" + setup_environment + check_dependencies + handle_env_file + + if [ ! -f "${ENV_FILE}" ]; then + prompt_for_configuration + fi + + start_services + display_services_info + + exit 0 +} + +# Call main function if the script is executed directly +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi diff --git a/tools/bats/test_start.bats b/tools/bats/test_start.bats new file mode 100644 index 0000000..d708b90 --- /dev/null +++ b/tools/bats/test_start.bats @@ -0,0 +1,157 @@ +#!/usr/bin/env bats + +# Load the functions from start.sh +# Try different possible locations based on working directory +START_SH_DIR="" +if [ -f "./start.sh" ]; then + START_SH_DIR="." +elif [ -f "../start.sh" ]; then + START_SH_DIR=".." +elif [ -f "../../start.sh" ]; then + START_SH_DIR="../.." +else + echo "Error: Cannot find start.sh file in ., .., or ../.." >&2 + echo "Current working directory: $(pwd)" >&2 + echo "Looking for: ./start.sh, ../start.sh, ../../start.sh" >&2 + exit 1 +fi + +# Source the files using the detected directory +if ! source "${START_SH_DIR}/start.sh"; then + echo "Failed to source ${START_SH_DIR}/start.sh" >&2 + exit 1 +fi + +if ! source "${START_SH_DIR}/bash_functions.sh"; then + echo "Failed to source ${START_SH_DIR}/bash_functions.sh" >&2 + exit 1 +fi + +# Initialize the OUTPUT variable +# Mock check_pcre_grep to avoid interactive prompt in tests +function check_pcre_grep() { + OUTPUT="echo -e" +} + +# Ensure BASE_NAME,BASE_DIR,ENV_FILE globals are set for tests +BASE_NAME="test-dev" +BASE_DIR="/tmp/bats_test_dir_$$" # Use unique temp directory +ENV_FILE="${BASE_DIR}/.env.${BASE_NAME}" + +# Mock functions and variables +function redact_sensitive_values() { + echo "redacted content" +} + +function export_save_variable() { + echo "export $1=$2" +} + +function ask_and_save() { + echo "$2: $3" +} + +function box_text_attention() { + echo "$2" +} + +function is_frequency_ready() { + return 0 +} + +# Setup runs before every test +setup() { + rm -rf "${BASE_DIR}" +} + +# Test show_help function +@test "show_help displays usage instructions" { + run show_help + [ "$status" -eq 0 ] + [[ "$output" == *"Usage: ./start.sh [options]"* ]] +} + +# Test parse_arguments function +@test "parse_arguments handles --help" { + run parse_arguments --help + [ "$status" -eq 0 ] + [[ "$output" == *"Usage: ./start.sh [options]"* ]] +} + +@test "parse_arguments handles --name" { + parse_arguments --name test_project + echo "BASE_NAME=$BASE_NAME" + [ "$BASE_NAME" == "test_project" ] +} + +@test "parse_arguments handles --skip-setup" { + parse_arguments --skip-setup + [ "$SKIP_CHAIN_SETUP" == "true" ] +} + +# Test setup_environment function +@test "setup_environment creates BASE_DIR" { + run setup_environment + [ "$status" -eq 0 ] + [ -d "${BASE_DIR}" ] +} + +# Test check_dependencies function (Assuming Docker and Docker Compose are installed) +@test "check_dependencies verifies Docker and Docker Compose" { + run check_dependencies + [ "$status" -eq 0 ] +} + +# Test handle_env_file function when ENV_FILE does not exist +@test "handle_env_file handles non-existing ENV_FILE" { + run handle_env_file + [ "$status" -eq 0 ] +} + +# Create a mock ENV_FILE for further tests +setup_file() { + OUTPUT="echo -e" + mkdir -p "${BASE_DIR}" + touch "${ENV_FILE}" +} + +# Test handle_env_file function when ENV_FILE exists +@test "handle_env_file handles existing ENV_FILE with yes" { + setup_file + function yesno() { + return 0 + } + run handle_env_file + [ "$status" -eq 0 ] + [[ "$output" == *"Loading environment values from file..."* ]] + +} + + +# Test handle_env_file function when ENV_FILE exists +@test "handle_env_file handles existing ENV_FILE with no" { + setup_file + function yesno() { + return 1 + } + run handle_env_file + [ "$status" -eq 0 ] + [[ "$output" == *"Removing previous saved environment..."* ]] + [ ! -f $ENV_FILE ] + +} + +# Test start_services function (Mocked Docker Compose commands) +@test "start_services starts services" { + function docker() { + echo "Mocked docker command: $@" + } + run start_services + [ "$status" -eq 0 ] +} + +# Test display_services_info function +@test "display_services_info displays service information" { + run display_services_info + [ "$status" -eq 0 ] +}