Compare commits

..

8 Commits

Author SHA1 Message Date
CI
3f68ae2235 chore: release version v1.56.5
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / Manual Approval (push) Blocked by required conditions
Build / 🛠 Build (1.17, 18.x, 27) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-03-19 13:07:55 +00:00
Dmitry Popov
0f7b6f75df Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-03-19 13:57:58 +01:00
Dmitry Popov
b048e8f5ca chore: added fallback chipher options 2025-03-19 13:55:39 +01:00
CI
9783dc45ff chore: release version v1.56.4 2025-03-19 11:36:46 +00:00
Dmitry Popov
badbefbade Revert "fix: cloak key error behavior (#288)" (#290)
This reverts commit 9b5ea2f84b.
2025-03-19 15:30:07 +04:00
CI
b6a265cfad chore: release version v1.56.3 2025-03-19 07:26:24 +00:00
guarzo
9b5ea2f84b fix: cloak key error behavior (#288) 2025-03-19 11:13:54 +04:00
guarzo
d8acfa5c05 refactor: standalone unit tests (#278)
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / Manual Approval (push) Blocked by required conditions
Build / 🛠 Build (1.17, 18.x, 27) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-03-18 21:37:52 +04:00
15 changed files with 1779 additions and 939 deletions

View File

@@ -157,7 +157,6 @@ jobs:
matrix:
platform:
- linux/amd64
- linux/arm64
steps:
- name: Prepare
run: |

View File

@@ -2,6 +2,25 @@
<!-- changelog -->
## [v1.56.5](https://github.com/wanderer-industries/wanderer/compare/v1.56.4...v1.56.5) (2025-03-19)
## [v1.56.4](https://github.com/wanderer-industries/wanderer/compare/v1.56.3...v1.56.4) (2025-03-19)
## [v1.56.3](https://github.com/wanderer-industries/wanderer/compare/v1.56.2...v1.56.3) (2025-03-19)
### Bug Fixes:
* cloak key error behavior (#288)
## [v1.56.2](https://github.com/wanderer-industries/wanderer/compare/v1.56.1...v1.56.2) (2025-03-18)

View File

@@ -1,4 +1,4 @@
.PHONY: deploy install cleanup start yarn migrate format test coverage versions
.PHONY: deploy install cleanup start yarn migrate format test coverage versions standalone-tests
ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
SHELL := /bin/bash
@@ -35,6 +35,11 @@ test t:
coverage cover co:
mix test --cover
unit-tests ut:
@echo "Running unit tests..."
@find test/unit -name "*.exs" -exec elixir {} \;
@echo "All unit tests completed."
versions v:
@echo "Tool Versions"
@cat .tool-versions

View File

@@ -3,20 +3,130 @@ defmodule WandererApp.Vault do
@impl GenServer
def init(config) do
cipher_key = decode_env!("CLOAK_KEY")
fallback_cipher_key = decode_env!("FALLBACK_CLOAK_KEY")
config =
Keyword.put(config, :ciphers,
default: {
Cloak.Ciphers.AES.GCM,
tag: "AES.GCM.V1", key: decode_env!("CLOAK_KEY"), iv_length: 12
tag: "AES.GCM.V1", key: cipher_key, iv_length: 12
},
fallback: {
Cloak.Ciphers.AES.GCM,
tag: "AES.GCM.V1", key: fallback_cipher_key, iv_length: 12
}
)
{:ok, config}
end
defp decode_env!(var) do
@impl Cloak.Vault
def encrypt(plaintext) do
with {:ok, config} <- Cloak.Vault.read_config(@table_name) do
Cloak.Vault.encrypt(config, plaintext)
end
end
@impl Cloak.Vault
def encrypt!(plaintext) do
case Cloak.Vault.read_config(@table_name) do
{:ok, config} ->
Cloak.Vault.encrypt!(config, plaintext)
{:error, error} ->
raise error
end
end
@impl Cloak.Vault
def encrypt(plaintext, label) do
with {:ok, config} <- Cloak.Vault.read_config(@table_name) do
Cloak.Vault.encrypt(config, plaintext, label)
end
end
@impl Cloak.Vault
def encrypt!(plaintext, label) do
case Cloak.Vault.read_config(@table_name) do
{:ok, config} ->
Cloak.Vault.encrypt!(config, plaintext, label)
{:error, error} ->
raise error
end
end
@impl Cloak.Vault
def decrypt(ciphertext) do
with {:ok, config} <- Cloak.Vault.read_config(@table_name) do
decrypt(config, ciphertext)
end
end
@impl Cloak.Vault
def decrypt!(ciphertext) do
case Cloak.Vault.read_config(@table_name) do
{:ok, config} ->
decrypt!(config, ciphertext)
{:error, error} ->
raise error
end
end
defp decode_env!(var, fallback_key \\ "OtPJXGfKNyOMWI7TdpcWgOlyNtD9AGSfoAdvEuTQIno=") do
var
|> System.get_env("OtPJXGfKNyOMWI7TdpcWgOlyNtD9AGSfoAdvEuTQIno=")
|> System.get_env(fallback_key)
|> Base.decode64!()
end
@doc false
def decrypt(config, ciphertext) do
case find_module_to_decrypt(config, ciphertext) do
nil ->
{:error, Cloak.MissingCipher.exception(vault: config[:vault], ciphertext: ciphertext)}
{_label, {module, opts}} ->
case module.decrypt(ciphertext, opts) do
{:ok, :error} ->
case find_fallback_module_to_decrypt(config, ciphertext) do
nil ->
{:ok, :error}
{_label, {module, opts}} ->
module.decrypt(ciphertext, opts)
end
{:ok, plaintext} ->
{:ok, plaintext}
error ->
error
end
end
end
@doc false
def decrypt!(config, ciphertext) do
case decrypt(config, ciphertext) do
{:ok, plaintext} ->
plaintext
{:error, error} ->
raise error
end
end
defp find_module_to_decrypt(config, ciphertext) do
Enum.find(config[:ciphers], fn {_label, {module, opts}} ->
module.can_decrypt?(ciphertext, opts)
end)
end
defp find_fallback_module_to_decrypt(config, ciphertext) do
Enum.find(config[:ciphers], fn {label, _} ->
label == :fallback
end)
end
end

View File

@@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do
@source_url "https://github.com/wanderer-industries/wanderer"
@version "1.56.2"
@version "1.56.5"
def project do
[

View File

@@ -1,13 +0,0 @@
# Wanderer API Testing Tool Configuration
# Generated on Thu Mar 6 14:52:00 UTC 2025
# Base configuration
HOST="http://localhost:4444"
MAP_SLUG="flygd"
MAP_API_KEY="589016d9-c9ac-48ef-ae74-7a55483b3cc2"
ACL_API_KEY=""
# Selected IDs
SELECTED_ACL_ID=""
SELECTED_SYSTEM_ID=""
CHARACTER_EVE_ID=""

View File

@@ -1,13 +0,0 @@
# Wanderer API Testing Tool Configuration
# Example configuration file - Copy to .api_test_config and modify as needed
# Base configuration
HOST="http://localhost:4000"
MAP_SLUG="flygd"
MAP_API_KEY="589016d9-c9ac-48ef-ae74-7a55483b3cc2"
ACL_API_KEY="acl-api-key-here"
# Selected IDs
SELECTED_ACL_ID="123"
SELECTED_SYSTEM_ID="31002019"
CHARACTER_EVE_ID="456"

View File

@@ -1,13 +0,0 @@
# Wanderer API Testing Tool Configuration
# Generated on Thu Mar 6 18:44:20 UTC 2025
# Base configuration
HOST="http://localhost:4444"
MAP_SLUG="flygd"
MAP_API_KEY="589016d9-c9ac-48ef-ae74-7a55483b3cc2"
ACL_API_KEY="116bd70e-2bbf-4a99-97ed-1869c09ab5bf"
# Selected IDs
SELECTED_ACL_ID="9c91d283-f49f-4f45-a21d-9bf53ce9d1fd"
SELECTED_SYSTEM_ID="30002768"
CHARACTER_EVE_ID="2115754172"

View File

@@ -1,894 +0,0 @@
#!/bin/bash
#==============================================================================
# Wanderer API Automated Testing Tool
#
# This script tests various endpoints of the Wanderer API.
#
# Features:
# - Uses strict mode (set -euo pipefail) for robust error handling.
# - Contains a DEBUG mode for extra logging (set DEBUG=1 to enable).
# - Validates configuration including a reachability test for the HOST.
# - Outputs a summary in plain text and optionally as JSON.
# - Exits with a nonzero code if any test fails.
#
# Usage:
# ./auto_test_api.sh
#
#==============================================================================
set -euo pipefail
IFS=$'\n\t'
# Set DEBUG=1 to enable extra logging
DEBUG=0
# Set VERBOSE=1 to print raw JSON responses for every test (default 0)
VERBOSE=0
# Set VERBOSE_SUMMARY=1 to output a JSON summary at the end (default 0)
VERBOSE_SUMMARY=0
# Colors for output
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Configuration file and default configuration
CONFIG_FILE=".auto_api_test_config"
HOST="http://localhost:4444" # Default host
MAP_SLUG=""
MAP_API_KEY=""
ACL_API_KEY=""
SELECTED_ACL_ID=""
SELECTED_SYSTEM_ID=""
CHARACTER_EVE_ID=""
TEST_RESULTS=()
FAILED_TESTS=()
# Global variables for last API response
LAST_JSON_RESPONSE=""
LAST_HTTP_CODE=""
#------------------------------------------------------------------------------
# Helper Functions
#------------------------------------------------------------------------------
debug() {
if [ "$DEBUG" -eq 1 ]; then
echo -e "${YELLOW}[DEBUG] $*${NC}" >&2
fi
}
print_header() {
echo -e "\n${BLUE}=== $1 ===${NC}\n"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}$1${NC}" >&2
}
print_error() {
echo -e "${RED}$1${NC}"
}
# Check if the host is reachable; accept any HTTP status code 200-399.
check_host_reachable() {
debug "Checking if host $HOST is reachable..."
local status
status=$(curl -s -o /dev/null -w "%{http_code}" "$HOST")
debug "HTTP status code for host: $status"
if [[ "$status" -ge 200 && "$status" -lt 400 ]]; then
print_success "Host $HOST is reachable."
else
print_error "Host $HOST is not reachable (HTTP code: $status). Please check the host URL."
exit 1
fi
}
# Load configuration from file
load_config() {
if [ -f "$CONFIG_FILE" ]; then
print_success "Loading configuration from $CONFIG_FILE"
source "$CONFIG_FILE"
return 0
else
print_warning "No configuration file found. Using default values."
return 1
fi
}
# Save configuration to file
save_config() {
print_success "Saving configuration to $CONFIG_FILE"
cat > "$CONFIG_FILE" << EOF
# Wanderer API Testing Tool Configuration
# Generated on $(date)
# Base configuration
HOST="$HOST"
MAP_SLUG="$MAP_SLUG"
MAP_API_KEY="$MAP_API_KEY"
ACL_API_KEY="$ACL_API_KEY"
# Selected IDs
SELECTED_ACL_ID="$SELECTED_ACL_ID"
SELECTED_SYSTEM_ID="$SELECTED_SYSTEM_ID"
CHARACTER_EVE_ID="$CHARACTER_EVE_ID"
EOF
chmod 600 "$CONFIG_FILE"
print_success "Configuration saved successfully."
}
# Make an API call using curl and capture response and HTTP code
call_api() {
local method=$1
local endpoint=$2
local api_key=$3
local data=${4:-""}
local curl_cmd=(curl -s -w "\n%{http_code}" -X "$method" -H "Content-Type: application/json")
if [ -n "$api_key" ]; then
curl_cmd+=(-H "Authorization: Bearer $api_key")
fi
if [ -n "$data" ]; then
curl_cmd+=(-d "$data")
fi
curl_cmd+=("$HOST$endpoint")
# Print debug command (mask API key)
local debug_cmd
debug_cmd=$(printf "%q " "${curl_cmd[@]}")
debug_cmd=$(echo "$debug_cmd" | sed "s/$api_key/API_KEY_HIDDEN/g")
print_warning "Executing: $debug_cmd"
local output
output=$("${curl_cmd[@]}")
LAST_HTTP_CODE=$(echo "$output" | tail -n1)
local response
response=$(echo "$output" | sed '$d')
echo "$response"
}
# Check that required variables are set
check_required_vars() {
local missing=false
if [ $# -eq 0 ]; then
if [ -z "$HOST" ]; then
print_error "HOST is not set. Please set it first."
missing=true
fi
if [ -z "$MAP_SLUG" ]; then
print_error "MAP_SLUG is not set. Please set it first."
missing=true
fi
if [ -z "$MAP_API_KEY" ]; then
print_error "MAP_API_KEY is not set. Please set it first."
missing=true
fi
else
for var in "$@"; do
if [ -z "${!var}" ]; then
print_error "$var is not set. Please set it first."
missing=true
fi
done
fi
$missing && return 1 || return 0
}
# Record a test result
record_test_result() {
local endpoint=$1
local status=$2
local message=$3
if [ "$status" = "success" ]; then
TEST_RESULTS+=("${GREEN}${NC} $endpoint - $message")
else
TEST_RESULTS+=("${RED}${NC} $endpoint - $message")
FAILED_TESTS+=("$endpoint - $message")
fi
}
# Process and validate the JSON response
check_response() {
local response=$1
local endpoint=$2
if [ -z "$(echo "$response" | xargs)" ]; then
if [ "$LAST_HTTP_CODE" = "200" ] || [ "$LAST_HTTP_CODE" = "204" ]; then
print_success "Received empty response, which is valid"
LAST_JSON_RESPONSE="{}"
return 0
else
record_test_result "$endpoint" "failure" "Empty response with HTTP code $LAST_HTTP_CODE"
return 1
fi
fi
if [ "$VERBOSE" -eq 1 ]; then
echo "Raw response from $endpoint:"
echo "$response" | head -n 20
fi
if echo "$response" | jq . > /dev/null 2>&1; then
LAST_JSON_RESPONSE="$response"
return 0
fi
local json_part
json_part=$(echo "$response" | grep -o '{.*}' || echo "")
if [ -z "$json_part" ] || ! echo "$json_part" | jq . > /dev/null 2>&1; then
json_part=$(echo "$response" | sed -n '/^{/,$p' | tr -d '\n')
fi
if [ -z "$json_part" ] || ! echo "$json_part" | jq . > /dev/null 2>&1; then
json_part=$(echo "$response" | sed -n '/{/,/}/p' | tr -d '\n')
fi
if [ -z "$json_part" ] || ! echo "$json_part" | jq . > /dev/null 2>&1; then
json_part=$(echo "$response" | awk '!(/^[<>*]/) {print}' | tr -d '\n')
fi
if [ -z "$json_part" ] || ! echo "$json_part" | jq . > /dev/null 2>&1; then
echo "Raw response from $endpoint:"
echo "$response"
record_test_result "$endpoint" "failure" "Invalid JSON response"
return 1
fi
local error
error=$(echo "$json_part" | jq -r '.error // empty')
if [ -n "$error" ]; then
echo "Raw response from $endpoint:"
echo "$response"
echo "Parsed JSON response from $endpoint:"
echo "$json_part" | jq '.'
record_test_result "$endpoint" "failure" "Error: $error"
return 1
fi
LAST_JSON_RESPONSE="$json_part"
return 0
}
# Get a random item from a JSON array using a jq path
get_random_item() {
local json=$1
local jq_path=$2
local count
count=$(echo "$json" | jq "$jq_path | length")
if [ "$count" -eq 0 ]; then
echo ""
return 1
fi
local random_index=$((RANDOM % count))
echo "$json" | jq -r "$jq_path[$random_index]"
}
#------------------------------------------------------------------------------
# API Test Functions
#------------------------------------------------------------------------------
test_list_characters() {
print_header "Testing GET /api/characters"
print_success "Calling API: GET /api/characters"
local response
response=$(call_api "GET" "/api/characters" "$MAP_API_KEY")
if ! check_response "$response" "GET /api/characters"; then
return 1
fi
local character_count
character_count=$(echo "$LAST_JSON_RESPONSE" | jq '.data | length')
if [ "$character_count" -gt 0 ]; then
record_test_result "GET /api/characters" "success" "Found $character_count characters"
if [ -z "$CHARACTER_EVE_ID" ]; then
local random_index=$((RANDOM % character_count))
print_success "Selecting character at index $random_index"
local random_character
random_character=$(echo "$LAST_JSON_RESPONSE" | jq ".data[$random_index]")
CHARACTER_EVE_ID=$(echo "$random_character" | jq -r '.eve_id')
local character_name
character_name=$(echo "$random_character" | jq -r '.name')
print_success "Selected random character: $character_name (EVE ID: $CHARACTER_EVE_ID)"
fi
return 0
else
record_test_result "GET /api/characters" "success" "No characters found"
return 0
fi
}
test_map_systems() {
print_header "Testing GET /api/map/systems"
if ! check_required_vars "MAP_SLUG" "MAP_API_KEY"; then
record_test_result "GET /api/map/systems" "failure" "Missing required variables"
return 1
fi
print_success "Calling API: GET /api/map/systems?slug=$MAP_SLUG"
local response
response=$(call_api "GET" "/api/map/systems?slug=$MAP_SLUG" "$MAP_API_KEY")
if ! check_response "$response" "GET /api/map/systems"; then
return 1
fi
local system_count
system_count=$(echo "$LAST_JSON_RESPONSE" | jq '.data | length')
print_success "System count: $system_count"
if [ "$system_count" -gt 0 ]; then
record_test_result "GET /api/map/systems" "success" "Found $system_count systems"
local random_index=$((RANDOM % system_count))
print_success "Selecting system at index $random_index"
echo "Data structure:"
echo "$LAST_JSON_RESPONSE" | jq '.data[0]'
local random_system
random_system=$(echo "$LAST_JSON_RESPONSE" | jq ".data[$random_index]")
echo "Selected system JSON:"
echo "$random_system"
SELECTED_SYSTEM_ID=$(echo "$random_system" | jq -r '.solar_system_id')
if [ -z "$SELECTED_SYSTEM_ID" ] || [ "$SELECTED_SYSTEM_ID" = "null" ]; then
SELECTED_SYSTEM_ID=$(echo "$random_system" | jq -r '.id // .system_id // empty')
if [ -z "$SELECTED_SYSTEM_ID" ] || [ "$SELECTED_SYSTEM_ID" = "null" ]; then
print_error "Could not find system ID in the response"
echo "Available fields:"
echo "$random_system" | jq 'keys'
record_test_result "GET /api/map/systems" "failure" "Could not extract system ID"
return 1
fi
fi
local system_name
system_name=$(echo "$random_system" | jq -r '.name // "Unknown"')
print_success "Selected random system: $system_name (ID: $SELECTED_SYSTEM_ID)"
return 0
else
record_test_result "GET /api/map/systems" "failure" "No systems found"
return 1
fi
}
test_map_system() {
print_header "Testing GET /api/map/system"
if [[ -z "$MAP_SLUG" || -z "$SELECTED_SYSTEM_ID" || -z "$MAP_API_KEY" ]]; then
record_test_result "GET /api/map/system" "failure" "Missing required variables"
return
fi
local response
response=$(call_api "GET" "/api/map/system?slug=$MAP_SLUG&id=$SELECTED_SYSTEM_ID" "$MAP_API_KEY")
print_warning "Response: $response"
local trimmed_response
trimmed_response=$(echo "$response" | xargs)
if [[ "$trimmed_response" == "{}" || "$trimmed_response" == '{"data":{}}' ]]; then
print_success "Received empty JSON response, which is valid"
record_test_result "GET /api/map/system" "success" "Received valid empty response"
return
fi
if ! check_response "$response" "GET /api/map/system"; then
return
fi
local json_data="$LAST_JSON_RESPONSE"
local has_data
has_data=$(echo "$json_data" | jq 'has("data")')
if [ "$has_data" != "true" ]; then
print_error "Response does not contain 'data' field"
echo "JSON Response:"
echo "$json_data" | jq .
record_test_result "GET /api/map/system" "failure" "Response does not contain 'data' field"
return
fi
local system_data
system_data=$(echo "$json_data" | jq -r '.data // empty')
if [ -z "$system_data" ] || [ "$system_data" = "null" ]; then
print_error "Could not find system data in response"
echo "JSON Response:"
echo "$json_data" | jq .
record_test_result "GET /api/map/system" "failure" "Could not find system data in response"
return
fi
local system_id
system_id=$(echo "$json_data" | jq -r '.data.solar_system_id // empty')
if [ -z "$system_id" ] || [ "$system_id" = "null" ]; then
print_error "Could not find solar_system_id in the system data"
echo "System Data:"
echo "$system_data" | jq .
record_test_result "GET /api/map/system" "failure" "Could not find solar_system_id in system data"
return
fi
print_success "Found system data with ID: $system_id"
record_test_result "GET /api/map/system" "success" "Found system data with ID: $system_id"
}
test_map_characters() {
print_header "Testing GET /api/map/characters"
if ! check_required_vars "MAP_SLUG" "MAP_API_KEY"; then
record_test_result "GET /api/map/characters" "failure" "Missing required variables"
return 1
fi
print_success "Calling API: GET /api/map/characters?slug=$MAP_SLUG"
local response
response=$(call_api "GET" "/api/map/characters?slug=$MAP_SLUG" "$MAP_API_KEY")
if ! check_response "$response" "GET /api/map/characters"; then
return 1
fi
local character_count
character_count=$(echo "$LAST_JSON_RESPONSE" | jq '.data | length')
record_test_result "GET /api/map/characters" "success" "Found $character_count tracked characters"
return 0
}
test_map_structure_timers() {
print_header "Testing GET /api/map/structure-timers"
if [[ -z "$MAP_SLUG" || -z "$MAP_API_KEY" ]]; then
record_test_result "GET /api/map/structure-timers" "failure" "Missing required variables"
return
fi
local response
response=$(call_api "GET" "/api/map/structure-timers?slug=$MAP_SLUG" "$MAP_API_KEY")
local trimmed_response
trimmed_response=$(echo "$response" | xargs)
if [[ "$trimmed_response" == '{"data":[]}' ]]; then
print_success "Found 0 structure timers"
record_test_result "GET /api/map/structure-timers" "success" "Found 0 structure timers"
fi
if ! check_response "$response" "GET /api/map/structure-timers"; then
return
fi
local timer_count
timer_count=$(echo "$LAST_JSON_RESPONSE" | jq '.data | length')
print_success "Found $timer_count structure timers"
record_test_result "GET /api/map/structure-timers" "success" "Found $timer_count structure timers"
if [ -n "$SELECTED_SYSTEM_ID" ]; then
print_header "Testing GET /api/map/structure-timers (filtered)"
local filtered_response
filtered_response=$(call_api "GET" "/api/map/structure-timers?slug=$MAP_SLUG&system_id=$SELECTED_SYSTEM_ID" "$MAP_API_KEY")
print_warning "(Structure Timers) - Filtered response: $filtered_response"
local trimmed_filtered
trimmed_filtered=$(echo "$filtered_response" | xargs)
if [[ "$trimmed_filtered" == '{"data":[]}' ]]; then
print_success "Found 0 filtered structure timers"
record_test_result "GET /api/map/structure-timers (filtered)" "success" "Found 0 filtered structure timers"
return
fi
if ! check_response "$filtered_response" "GET /api/map/structure-timers (filtered)"; then
return
fi
local filtered_count
filtered_count=$(echo "$LAST_JSON_RESPONSE" | jq '.data | length')
print_success "Found $filtered_count filtered structure timers"
record_test_result "GET /api/map/structure-timers (filtered)" "success" "Found $filtered_count filtered structure timers"
fi
}
test_map_systems_kills() {
print_header "Testing GET /api/map/systems-kills"
if [[ -z "$MAP_SLUG" || -z "$MAP_API_KEY" ]]; then
record_test_result "GET /api/map/systems-kills" "failure" "Missing required variables"
return
fi
# Use the correct parameter name: hours
local response
response=$(call_api "GET" "/api/map/systems-kills?slug=$MAP_SLUG&hours=1" "$MAP_API_KEY")
print_warning "(Systems Kills) - Response: $response"
if ! check_response "$response" "GET /api/map/systems-kills"; then
return
fi
local json_data="$LAST_JSON_RESPONSE"
if [ "$VERBOSE" -eq 1 ]; then
echo "JSON Response:"; echo "$json_data" | jq .
fi
local has_data
has_data=$(echo "$json_data" | jq 'has("data")')
if [ "$has_data" != "true" ]; then
print_error "Response does not contain 'data' field"
if [ "$VERBOSE" -eq 1 ]; then
echo "JSON Response:"; echo "$json_data" | jq .
fi
record_test_result "GET /api/map/systems-kills" "failure" "Response does not contain 'data' field"
return
fi
local systems_count
systems_count=$(echo "$json_data" | jq '.data | length')
print_success "Found kill data for $systems_count systems"
record_test_result "GET /api/map/systems-kills" "success" "Found kill data for $systems_count systems"
print_header "Testing GET /api/map/systems-kills (filtered)"
local filter_url="/api/map/systems-kills?slug=$MAP_SLUG&hours=1"
if [ -n "$SELECTED_SYSTEM_ID" ]; then
filter_url="$filter_url&system_id=$SELECTED_SYSTEM_ID"
print_success "Using system_id filter to reduce response size"
fi
local filtered_response
filtered_response=$(call_api "GET" "$filter_url" "$MAP_API_KEY")
local trimmed_filtered
trimmed_filtered=$(echo "$filtered_response" | xargs)
if [[ "$trimmed_filtered" == '{"data":[]}' ]]; then
print_success "Found 0 filtered systems with kill data"
record_test_result "GET /api/map/systems-kills (filtered)" "success" "Found 0 filtered systems with kill data"
return
fi
if [[ "$trimmed_filtered" == '{"data":'* ]]; then
print_success "Received valid JSON response (large data)"
record_test_result "GET /api/map/systems-kills (filtered)" "success" "Received valid JSON response with kill data"
return
fi
if ! check_response "$filtered_response" "GET /api/map/systems-kills (filtered)"; then
return
fi
local filtered_count
filtered_count=$(echo "$LAST_JSON_RESPONSE" | jq '.data | length')
print_success "Found filtered kill data for $filtered_count systems"
record_test_result "GET /api/map/systems-kills (filtered)" "success" "Found filtered kill data for $filtered_count systems"
}
test_map_acls() {
print_header "Testing GET /api/map/acls"
if ! check_required_vars "MAP_SLUG" "MAP_API_KEY"; then
record_test_result "GET /api/map/acls" "failure" "Missing required variables"
return 1
fi
print_success "Calling API: GET /api/map/acls?slug=$MAP_SLUG"
local response
response=$(call_api "GET" "/api/map/acls?slug=$MAP_SLUG" "$MAP_API_KEY")
if ! check_response "$response" "GET /api/map/acls"; then
return 1
fi
local acl_count
acl_count=$(echo "$LAST_JSON_RESPONSE" | jq '.data | length')
record_test_result "GET /api/map/acls" "success" "Found $acl_count ACLs"
if [ "$acl_count" -gt 0 ]; then
local random_acl
random_acl=$(get_random_item "$LAST_JSON_RESPONSE" ".data")
SELECTED_ACL_ID=$(echo "$random_acl" | jq -r '.id')
local acl_name
acl_name=$(echo "$random_acl" | jq -r '.name')
print_success "Selected random ACL: $acl_name (ID: $SELECTED_ACL_ID)"
else
print_warning "No ACLs found to select for future tests"
fi
return 0
}
test_create_acl() {
print_header "Testing POST /api/map/acls"
if ! check_required_vars "MAP_SLUG" "MAP_API_KEY"; then
record_test_result "POST /api/map/acls" "failure" "Missing required variables"
return 1
fi
if [ -z "$CHARACTER_EVE_ID" ]; then
print_warning "No character EVE ID selected. Fetching characters..."
print_success "Calling API: GET /api/characters"
local characters_response
characters_response=$(call_api "GET" "/api/characters" "$MAP_API_KEY")
if ! check_response "$characters_response" "GET /api/characters"; then
record_test_result "POST /api/map/acls" "failure" "Failed to get characters"
return 1
fi
local character_count
character_count=$(echo "$LAST_JSON_RESPONSE" | jq '.data | length')
if [ "$character_count" -eq 0 ]; then
record_test_result "POST /api/map/acls" "failure" "No characters found"
return 1
fi
local random_index=$((RANDOM % character_count))
print_success "Selecting character at index $random_index"
local random_character
random_character=$(echo "$LAST_JSON_RESPONSE" | jq ".data[$random_index]")
CHARACTER_EVE_ID=$(echo "$random_character" | jq -r '.eve_id')
local character_name
character_name=$(echo "$random_character" | jq -r '.name')
print_success "Selected random character: $character_name (EVE ID: $CHARACTER_EVE_ID)"
fi
local acl_name="Auto Test ACL $(date +%s)"
local acl_description="Created by auto_test_api.sh on $(date)"
local data="{\"acl\": {\"name\": \"$acl_name\", \"owner_eve_id\": $CHARACTER_EVE_ID, \"description\": \"$acl_description\"}}"
print_success "Calling API: POST /api/map/acls?slug=$MAP_SLUG"
print_success "Data: $data"
local response
response=$(call_api "POST" "/api/map/acls?slug=$MAP_SLUG" "$MAP_API_KEY" "$data")
if ! check_response "$response" "POST /api/map/acls"; then
return 1
fi
local new_acl_id
new_acl_id=$(echo "$LAST_JSON_RESPONSE" | jq -r '.data.id // empty')
local new_api_key
new_api_key=$(echo "$LAST_JSON_RESPONSE" | jq -r '.data.api_key // empty')
if [ -n "$new_acl_id" ] && [ -n "$new_api_key" ]; then
record_test_result "POST /api/map/acls" "success" "Created new ACL with ID: $new_acl_id"
SELECTED_ACL_ID=$new_acl_id
ACL_API_KEY=$new_api_key
print_success "Using the new ACL (ID: $SELECTED_ACL_ID) and its API key for further operations"
save_config
return 0
else
record_test_result "POST /api/map/acls" "failure" "Failed to extract ACL ID or API key from response"
return 1
fi
}
test_show_acl() {
print_header "Testing GET /api/acls/:id"
if [ -z "$SELECTED_ACL_ID" ] || [ -z "$ACL_API_KEY" ]; then
record_test_result "GET /api/acls/:id" "failure" "Missing ACL ID or API key"
return 1
fi
print_success "Calling API: GET /api/acls/$SELECTED_ACL_ID"
local response
response=$(call_api "GET" "/api/acls/$SELECTED_ACL_ID" "$ACL_API_KEY")
if ! check_response "$response" "GET /api/acls/:id"; then
return 1
fi
local acl_name
acl_name=$(echo "$LAST_JSON_RESPONSE" | jq -r '.data.name // empty')
if [ -n "$acl_name" ]; then
record_test_result "GET /api/acls/:id" "success" "Found ACL: $acl_name"
return 0
else
record_test_result "GET /api/acls/:id" "failure" "ACL data not found"
return 1
fi
}
test_update_acl() {
print_header "Testing PUT /api/acls/:id"
if [ -z "$SELECTED_ACL_ID" ] || [ -z "$ACL_API_KEY" ]; then
record_test_result "PUT /api/acls/:id" "failure" "Missing ACL ID or API key"
return 1
fi
local new_name="Updated Auto Test ACL $(date +%s)"
local new_description="Updated by auto_test_api.sh on $(date)"
local data="{\"acl\": {\"name\": \"$new_name\", \"description\": \"$new_description\"}}"
print_success "Calling API: PUT /api/acls/$SELECTED_ACL_ID"
print_success "Data: $data"
local response
response=$(call_api "PUT" "/api/acls/$SELECTED_ACL_ID" "$ACL_API_KEY" "$data")
if ! check_response "$response" "PUT /api/acls/:id"; then
return 1
fi
local updated_name
updated_name=$(echo "$LAST_JSON_RESPONSE" | jq -r '.data.name // empty')
if [ "$updated_name" = "$new_name" ]; then
record_test_result "PUT /api/acls/:id" "success" "Updated ACL name to: $updated_name"
return 0
else
record_test_result "PUT /api/acls/:id" "failure" "Failed to update ACL name"
return 1
fi
}
test_create_acl_member() {
print_header "Testing POST /api/acls/:acl_id/members"
if [ -z "$SELECTED_ACL_ID" ] || [ -z "$ACL_API_KEY" ]; then
record_test_result "POST /api/acls/:acl_id/members" "failure" "Missing ACL ID or API key"
return 1
fi
if [ -z "$CHARACTER_EVE_ID" ]; then
print_warning "No character EVE ID selected. Fetching characters..."
print_success "Calling API: GET /api/characters"
local characters_response
characters_response=$(call_api "GET" "/api/characters" "$MAP_API_KEY")
if ! check_response "$characters_response" "GET /api/characters"; then
record_test_result "POST /api/acls/:acl_id/members" "failure" "Failed to get characters"
return 1
fi
local character_count
character_count=$(echo "$LAST_JSON_RESPONSE" | jq '.data | length')
if [ "$character_count" -eq 0 ]; then
record_test_result "POST /api/acls/:acl_id/members" "failure" "No characters found"
return 1
fi
local random_index=$((RANDOM % character_count))
print_success "Selecting character at index $random_index"
local random_character
random_character=$(echo "$LAST_JSON_RESPONSE" | jq ".data[$random_index]")
CHARACTER_EVE_ID=$(echo "$random_character" | jq -r '.eve_id')
local character_name
character_name=$(echo "$random_character" | jq -r '.name')
print_success "Selected random character: $character_name (EVE ID: $CHARACTER_EVE_ID)"
fi
local data="{\"member\": {\"eve_character_id\": $CHARACTER_EVE_ID, \"role\": \"member\"}}"
print_success "Calling API: POST /api/acls/$SELECTED_ACL_ID/members"
print_success "Data: $data"
local response
response=$(call_api "POST" "/api/acls/$SELECTED_ACL_ID/members" "$ACL_API_KEY" "$data")
if ! check_response "$response" "POST /api/acls/:acl_id/members"; then
return 1
fi
local member_id
member_id=$(echo "$LAST_JSON_RESPONSE" | jq -r '.data.id // empty')
if [ -n "$member_id" ]; then
record_test_result "POST /api/acls/:acl_id/members" "success" "Created new member with ID: $member_id"
MEMBER_ID=$CHARACTER_EVE_ID
return 0
else
record_test_result "POST /api/acls/:acl_id/members" "failure" "Failed to create member"
return 1
fi
}
test_update_acl_member() {
print_header "Testing PUT /api/acls/:acl_id/members/:member_id"
if [ -z "$SELECTED_ACL_ID" ] || [ -z "$ACL_API_KEY" ] || [ -z "$MEMBER_ID" ]; then
record_test_result "PUT /api/acls/:acl_id/members/:member_id" "failure" "Missing ACL ID, API key, or member ID"
return 1
fi
local data="{\"member\": {\"role\": \"member\"}}"
print_success "Calling API: PUT /api/acls/$SELECTED_ACL_ID/members/$MEMBER_ID"
print_success "Data: $data"
local response
response=$(call_api "PUT" "/api/acls/$SELECTED_ACL_ID/members/$MEMBER_ID" "$ACL_API_KEY" "$data")
if ! check_response "$response" "PUT /api/acls/:acl_id/members/:member_id"; then
return 1
fi
local updated_role
updated_role=$(echo "$LAST_JSON_RESPONSE" | jq -r '.data.role // empty')
if [ "$updated_role" = "member" ]; then
record_test_result "PUT /api/acls/:acl_id/members/:member_id" "success" "Updated member role to: $updated_role"
return 0
else
record_test_result "PUT /api/acls/:acl_id/members/:member_id" "failure" "Failed to update member role"
return 1
fi
}
test_delete_acl_member() {
print_header "Testing DELETE /api/acls/:acl_id/members/:member_id"
if [ -z "$SELECTED_ACL_ID" ] || [ -z "$ACL_API_KEY" ] || [ -z "$MEMBER_ID" ]; then
record_test_result "DELETE /api/acls/:acl_id/members/:member_id" "failure" "Missing ACL ID, API key, or member ID"
return 1
fi
print_success "Calling API: DELETE /api/acls/$SELECTED_ACL_ID/members/$MEMBER_ID"
local response
response=$(call_api "DELETE" "/api/acls/$SELECTED_ACL_ID/members/$MEMBER_ID" "$ACL_API_KEY")
if ! check_response "$response" "DELETE /api/acls/:acl_id/members/:member_id"; then
return 1
fi
record_test_result "DELETE /api/acls/:acl_id/members/:member_id" "success" "Deleted member with ID: $MEMBER_ID"
MEMBER_ID=""
return 0
}
test_system_static_info() {
print_header "Testing GET /api/common/system-static-info"
if [ -z "$SELECTED_SYSTEM_ID" ]; then
record_test_result "GET /api/common/system-static-info" "failure" "No system ID selected"
return 1
fi
print_success "Calling API: GET /api/common/system-static-info?id=$SELECTED_SYSTEM_ID"
local response
response=$(call_api "GET" "/api/common/system-static-info?id=$SELECTED_SYSTEM_ID" "$MAP_API_KEY")
if ! check_response "$response" "GET /api/common/system-static-info"; then
return 1
fi
local system_count
system_count=$(echo "$LAST_JSON_RESPONSE" | jq 'length')
record_test_result "GET /api/common/system-static-info" "success" "Found static info for $system_count systems"
return 0
}
#------------------------------------------------------------------------------
# Configuration and Main Menu Functions
#------------------------------------------------------------------------------
set_config() {
print_header "Configuration"
echo -e "Current configuration:"
[ -n "$HOST" ] && echo -e " Host: ${BLUE}$HOST${NC}"
[ -n "$MAP_SLUG" ] && echo -e " Map Slug: ${BLUE}$MAP_SLUG${NC}"
[ -n "$MAP_API_KEY" ] && echo -e " Map API Key: ${BLUE}${MAP_API_KEY:0:8}...${NC}"
read -p "Enter host (default: $HOST): " input_host
[ -n "$input_host" ] && HOST="$input_host"
read -p "Enter map slug: " input_map_slug
[ -n "$input_map_slug" ] && MAP_SLUG="$input_map_slug"
read -p "Enter map API key: " input_map_api_key
[ -n "$input_map_api_key" ] && MAP_API_KEY="$input_map_api_key"
# Reset IDs to force fresh data
SELECTED_SYSTEM_ID=""
SELECTED_ACL_ID=""
ACL_API_KEY=""
CHARACTER_EVE_ID=""
save_config
}
run_all_tests() {
print_header "Running all API tests"
TEST_RESULTS=()
FAILED_TESTS=()
if ! command -v jq &> /dev/null; then
print_error "jq is required for this script to work. Please install it first."
exit 1
fi
if ! check_required_vars "MAP_SLUG" "MAP_API_KEY"; then
print_error "Please set MAP_SLUG and MAP_API_KEY before running tests."
exit 1
fi
check_host_reachable
test_list_characters
if test_map_systems; then
test_map_system
else
print_error "Skipping test_map_system because test_map_systems failed"
record_test_result "GET /api/map/system" "failure" "Skipped because test_map_systems failed"
fi
test_map_characters
test_map_structure_timers
test_map_systems_kills
test_map_acls
if test_create_acl; then
test_show_acl
test_update_acl
if test_create_acl_member; then
test_update_acl_member
test_delete_acl_member
else
print_error "Skipping ACL member tests because test_create_acl_member failed"
record_test_result "PUT /api/acls/:acl_id/members/:member_id" "failure" "Skipped because test_create_acl_member failed"
record_test_result "DELETE /api/acls/:acl_id/members/:member_id" "failure" "Skipped because test_create_acl_member failed"
fi
else
print_error "Skipping ACL tests because test_create_acl failed"
record_test_result "GET /api/acls/:id" "failure" "Skipped because test_create_acl failed"
record_test_result "PUT /api/acls/:id" "failure" "Skipped because test_create_acl failed"
record_test_result "POST /api/acls/:acl_id/members" "failure" "Skipped because test_create_acl failed"
record_test_result "PUT /api/acls/:acl_id/members/:member_id" "failure" "Skipped because test_create_acl failed"
record_test_result "DELETE /api/acls/:acl_id/members/:member_id" "failure" "Skipped because test_create_acl failed"
fi
test_system_static_info
print_header "Test Results"
for result in "${TEST_RESULTS[@]}"; do
echo -e "$result"
done
local total_tests=${#TEST_RESULTS[@]}
local failed_tests=${#FAILED_TESTS[@]}
local passed_tests=$((total_tests - failed_tests))
print_header "Summary"
echo -e "Total tests: $total_tests"
echo -e "Passed: ${GREEN}$passed_tests${NC}"
echo -e "Failed: ${RED}$failed_tests${NC}"
if [ $failed_tests -gt 0 ]; then
print_header "Failed Tests"
for failed in "${FAILED_TESTS[@]}"; do
echo -e "${RED}${NC} $failed"
done
fi
if [ "$VERBOSE_SUMMARY" -eq 1 ]; then
summary_json=$(jq -n --arg total "$total_tests" --arg passed "$passed_tests" --arg failed "$failed_tests" \
'{total_tests: $total_tests|tonumber, passed: $passed|tonumber, failed: $failed|tonumber}')
echo "JSON Summary:"; echo "$summary_json" | jq .
fi
save_config
if [ $failed_tests -gt 0 ]; then
exit 1
else
exit 0
fi
}
#------------------------------------------------------------------------------
# Main Menu and Entry Point
#------------------------------------------------------------------------------
main() {
print_header "Wanderer API Automated Testing Tool"
load_config
if [ -z "$MAP_SLUG" ] || [ -z "$MAP_API_KEY" ]; then
print_warning "MAP_SLUG or MAP_API_KEY not set. Let's configure them now."
set_config
fi
echo -e "What would you like to do?"
echo "1) Run all tests"
echo "2) Set configuration"
echo "3) Exit"
read -p "Enter your choice: " choice
case $choice in
1) run_all_tests ;;
2) set_config ;;
3) exit 0 ;;
*) print_error "Invalid choice"; main ;;
esac
}
# Start the script
main

View File

@@ -0,0 +1,334 @@
# Standalone test for the CharacterAPIController
#
# This file can be run directly with:
# elixir test/standalone/character_api_controller_test.exs
#
# It doesn't require any database connections or external dependencies.
# Start ExUnit
ExUnit.start()
defmodule CharacterAPIControllerTest do
use ExUnit.Case
# Mock modules to simulate the behavior of the controller's dependencies
defmodule MockUtil do
def require_param(params, key) do
case params[key] do
nil -> {:error, "Missing required param: #{key}"}
"" -> {:error, "Param #{key} cannot be empty"}
val -> {:ok, val}
end
end
def parse_int(str) do
case Integer.parse(str) do
{num, ""} -> {:ok, num}
_ -> {:error, "Invalid integer for param id=#{str}"}
end
end
def parse_bool(str) do
case str do
"true" -> {:ok, true}
"false" -> {:ok, false}
_ -> {:error, "Invalid boolean value: #{str}"}
end
end
end
defmodule MockCharacterRepo do
# In-memory storage for character tracking data
def init_storage do
:ets.new(:character_tracking, [:set, :public, :named_table])
# Initialize with some test data
:ets.insert(:character_tracking, {"user1", [
%{eve_id: "123456", name: "Character One", tracked: true, followed: true},
%{eve_id: "234567", name: "Character Two", tracked: true, followed: false},
%{eve_id: "345678", name: "Character Three", tracked: false, followed: false}
]})
:ets.insert(:character_tracking, {"user2", [
%{eve_id: "456789", name: "Character Four", tracked: true, followed: true}
]})
end
def get_tracking_data(user_id) do
case :ets.lookup(:character_tracking, user_id) do
[{^user_id, data}] -> {:ok, data}
[] -> {:ok, []}
end
end
def update_tracking_data(user_id, new_data) do
:ets.insert(:character_tracking, {user_id, new_data})
{:ok, new_data}
end
def toggle_character_follow(user_id, character_id, follow_state) do
case get_tracking_data(user_id) do
{:ok, data} ->
# Find the character and update its followed state
updated_data = Enum.map(data, fn char ->
if char.eve_id == character_id do
%{char | followed: follow_state}
else
char
end
end)
# Update the storage
update_tracking_data(user_id, updated_data)
# Return the updated character
updated_char = Enum.find(updated_data, fn char -> char.eve_id == character_id end)
{:ok, updated_char}
error -> error
end
end
def toggle_character_track(user_id, character_id, track_state) do
case get_tracking_data(user_id) do
{:ok, data} ->
# Find the character and update its tracked state
updated_data = Enum.map(data, fn char ->
if char.eve_id == character_id do
%{char | tracked: track_state}
else
char
end
end)
# Update the storage
update_tracking_data(user_id, updated_data)
# Return the updated character
updated_char = Enum.find(updated_data, fn char -> char.eve_id == character_id end)
{:ok, updated_char}
error -> error
end
end
end
defmodule MockTrackingUtils do
def check_tracking_consistency(tracking_data) do
# Log warnings for characters that are followed but not tracked
inconsistent_chars = Enum.filter(tracking_data, fn char ->
char[:followed] == true && char[:tracked] == false
end)
if length(inconsistent_chars) > 0 do
Enum.each(inconsistent_chars, fn char ->
eve_id = Map.get(char, :eve_id, "unknown")
name = Map.get(char, :name, "Unknown Character")
IO.puts("WARNING: Inconsistent state detected - Character (ID: #{eve_id}, Name: #{name}) is followed but not tracked")
end)
end
# Return the original data unchanged
tracking_data
end
end
# Mock controller that uses our mock dependencies
defmodule MockCharacterAPIController do
# Simplified version of toggle_follow from CharacterAPIController
def toggle_follow(params, user_id) do
with {:ok, character_id} <- MockUtil.require_param(params, "character_id"),
{:ok, follow_str} <- MockUtil.require_param(params, "follow"),
{:ok, follow} <- MockUtil.parse_bool(follow_str) do
case MockCharacterRepo.toggle_character_follow(user_id, character_id, follow) do
{:ok, updated_char} ->
# Get all tracking data to check consistency
{:ok, all_tracking} = MockCharacterRepo.get_tracking_data(user_id)
# Check for inconsistencies (characters followed but not tracked)
MockTrackingUtils.check_tracking_consistency(all_tracking)
# Return the updated character
{:ok, %{data: updated_char}}
{:error, reason} ->
{:error, :internal_server_error, "Failed to update character: #{reason}"}
end
else
{:error, msg} ->
{:error, :bad_request, msg}
end
end
# Simplified version of toggle_track from CharacterAPIController
def toggle_track(params, user_id) do
with {:ok, character_id} <- MockUtil.require_param(params, "character_id"),
{:ok, track_str} <- MockUtil.require_param(params, "track"),
{:ok, track} <- MockUtil.parse_bool(track_str) do
# If we're untracking a character, we should also unfollow it
result = if track == false do
# First unfollow if needed
MockCharacterRepo.toggle_character_follow(user_id, character_id, false)
# Then untrack
MockCharacterRepo.toggle_character_track(user_id, character_id, false)
else
# Just track
MockCharacterRepo.toggle_character_track(user_id, character_id, true)
end
case result do
{:ok, updated_char} ->
# Get all tracking data to check consistency
{:ok, all_tracking} = MockCharacterRepo.get_tracking_data(user_id)
# Check for inconsistencies (characters followed but not tracked)
MockTrackingUtils.check_tracking_consistency(all_tracking)
# Return the updated character
{:ok, %{data: updated_char}}
{:error, reason} ->
{:error, :internal_server_error, "Failed to update character: #{reason}"}
end
else
{:error, msg} ->
{:error, :bad_request, msg}
end
end
# Simplified version of list_tracking from CharacterAPIController
def list_tracking(user_id) do
case MockCharacterRepo.get_tracking_data(user_id) do
{:ok, tracking_data} ->
# Check for inconsistencies
checked_data = MockTrackingUtils.check_tracking_consistency(tracking_data)
# Return the data
{:ok, %{data: checked_data}}
{:error, reason} ->
{:error, :internal_server_error, "Failed to get tracking data: #{reason}"}
end
end
end
# Setup for tests
setup do
# Initialize the mock storage
MockCharacterRepo.init_storage()
:ok
end
describe "toggle_follow/2" do
test "follows a character successfully" do
params = %{"character_id" => "345678", "follow" => "true"}
result = MockCharacterAPIController.toggle_follow(params, "user1")
assert {:ok, %{data: data}} = result
assert data.eve_id == "345678"
assert data.name == "Character Three"
assert data.followed == true
assert data.tracked == false
# This should have created an inconsistency (followed but not tracked)
# The check_tracking_consistency function should have logged a warning
end
test "unfollows a character successfully" do
params = %{"character_id" => "123456", "follow" => "false"}
result = MockCharacterAPIController.toggle_follow(params, "user1")
assert {:ok, %{data: data}} = result
assert data.eve_id == "123456"
assert data.followed == false
assert data.tracked == true
end
test "returns error when character_id is missing" do
params = %{"follow" => "true"}
result = MockCharacterAPIController.toggle_follow(params, "user1")
assert {:error, :bad_request, message} = result
assert message == "Missing required param: character_id"
end
test "returns error when follow is not a valid boolean" do
params = %{"character_id" => "123456", "follow" => "not-a-boolean"}
result = MockCharacterAPIController.toggle_follow(params, "user1")
assert {:error, :bad_request, message} = result
assert message =~ "Invalid boolean value"
end
end
describe "toggle_track/2" do
test "tracks a character successfully" do
params = %{"character_id" => "345678", "track" => "true"}
result = MockCharacterAPIController.toggle_track(params, "user1")
assert {:ok, %{data: data}} = result
assert data.eve_id == "345678"
assert data.tracked == true
end
test "untracks and unfollows a character" do
# First, make sure the character is followed
follow_params = %{"character_id" => "123456", "follow" => "true"}
MockCharacterAPIController.toggle_follow(follow_params, "user1")
# Now untrack the character
params = %{"character_id" => "123456", "track" => "false"}
result = MockCharacterAPIController.toggle_track(params, "user1")
assert {:ok, %{data: data}} = result
assert data.eve_id == "123456"
assert data.tracked == false
assert data.followed == false # Should also be unfollowed
end
test "returns error when character_id is missing" do
params = %{"track" => "true"}
result = MockCharacterAPIController.toggle_track(params, "user1")
assert {:error, :bad_request, message} = result
assert message == "Missing required param: character_id"
end
test "returns error when track is not a valid boolean" do
params = %{"character_id" => "123456", "track" => "not-a-boolean"}
result = MockCharacterAPIController.toggle_track(params, "user1")
assert {:error, :bad_request, message} = result
assert message =~ "Invalid boolean value"
end
end
describe "list_tracking/1" do
test "returns tracking data for a user" do
result = MockCharacterAPIController.list_tracking("user1")
assert {:ok, %{data: data}} = result
assert length(data) == 3
# Check that the data contains the expected characters
char_one = Enum.find(data, fn char -> char.eve_id == "123456" end)
assert char_one.name == "Character One"
assert char_one.tracked == true
assert char_one.followed == true
char_two = Enum.find(data, fn char -> char.eve_id == "234567" end)
assert char_two.name == "Character Two"
assert char_two.tracked == true
assert char_two.followed == false
end
test "returns empty list for user with no tracking data" do
result = MockCharacterAPIController.list_tracking("non-existent-user")
assert {:ok, %{data: data}} = result
assert data == []
end
end
end

View File

@@ -0,0 +1,297 @@
# Standalone test for the CommonAPIController
#
# This file can be run directly with:
# elixir test/standalone/common_api_controller_test.exs
#
# It doesn't require any database connections or external dependencies.
# Start ExUnit
ExUnit.start()
defmodule CommonAPIControllerTest do
use ExUnit.Case
# Mock modules to simulate the behavior of the controller's dependencies
defmodule MockUtil do
def require_param(params, key) do
case params[key] do
nil -> {:error, "Missing required param: #{key}"}
"" -> {:error, "Param #{key} cannot be empty"}
val -> {:ok, val}
end
end
def parse_int(str) do
case Integer.parse(str) do
{num, ""} -> {:ok, num}
_ -> {:error, "Invalid integer for param id=#{str}"}
end
end
end
defmodule MockCachedInfo do
def get_system_static_info(30000142) do
{:ok, %{
solar_system_id: 30000142,
region_id: 10000002,
constellation_id: 20000020,
solar_system_name: "Jita",
solar_system_name_lc: "jita",
constellation_name: "Kimotoro",
region_name: "The Forge",
system_class: 0,
security: "0.9",
type_description: "High Security",
class_title: "High Sec",
is_shattered: false,
effect_name: nil,
effect_power: nil,
statics: [],
wandering: [],
triglavian_invasion_status: nil,
sun_type_id: 45041
}}
end
def get_system_static_info(31000005) do
{:ok, %{
solar_system_id: 31000005,
region_id: 11000000,
constellation_id: 21000000,
solar_system_name: "J123456",
solar_system_name_lc: "j123456",
constellation_name: "Unknown",
region_name: "Wormhole Space",
system_class: 1,
security: "-0.9",
type_description: "Wormhole",
class_title: "Class 1",
is_shattered: false,
effect_name: "Wolf-Rayet Star",
effect_power: 1,
statics: ["N110"],
wandering: ["K162"],
triglavian_invasion_status: nil,
sun_type_id: 45042
}}
end
def get_system_static_info(_) do
{:error, :not_found}
end
def get_wormhole_types do
{:ok, [
%{
name: "N110",
dest: 1,
lifetime: "16h",
total_mass: 500000000,
max_mass_per_jump: 20000000,
mass_regen: 0
}
]}
end
def get_wormhole_classes! do
[
%{
id: 1,
title: "Class 1 Wormhole",
short_name: "C1"
}
]
end
end
# Mock controller that uses our mock dependencies
defmodule MockCommonAPIController do
# Simplified version of show_system_static from CommonAPIController
def show_system_static(params) do
with {:ok, solar_system_str} <- MockUtil.require_param(params, "id"),
{:ok, solar_system_id} <- MockUtil.parse_int(solar_system_str) do
case MockCachedInfo.get_system_static_info(solar_system_id) do
{:ok, system} ->
# Get basic system data
data = static_system_to_json(system)
# Enhance with wormhole type information if statics exist
enhanced_data = enhance_with_static_details(data)
# Return the enhanced data
{:ok, %{data: enhanced_data}}
{:error, :not_found} ->
{:error, :not_found, "System not found"}
end
else
{:error, msg} ->
{:error, :bad_request, msg}
end
end
# Helper function to convert a system to JSON format
defp static_system_to_json(system) do
system
|> Map.take([
:solar_system_id,
:region_id,
:constellation_id,
:solar_system_name,
:solar_system_name_lc,
:constellation_name,
:region_name,
:system_class,
:security,
:type_description,
:class_title,
:is_shattered,
:effect_name,
:effect_power,
:statics,
:wandering,
:triglavian_invasion_status,
:sun_type_id
])
end
# Helper function to enhance system data with wormhole type information
defp enhance_with_static_details(data) do
if data[:statics] && length(data[:statics]) > 0 do
# Add the enhanced static details to the response
Map.put(data, :static_details, get_static_details(data[:statics]))
else
# No statics, return the original data
data
end
end
# Helper function to get detailed information for each static wormhole
defp get_static_details(statics) do
# Get wormhole data from CachedInfo
{:ok, wormhole_types} = MockCachedInfo.get_wormhole_types()
wormhole_classes = MockCachedInfo.get_wormhole_classes!()
# Create a map of wormhole classes by ID for quick lookup
classes_by_id = Enum.reduce(wormhole_classes, %{}, fn class, acc ->
Map.put(acc, class.id, class)
end)
# Find detailed information for each static
Enum.map(statics, fn static_name ->
# Find the wormhole type by name
wh_type = Enum.find(wormhole_types, fn type -> type.name == static_name end)
if wh_type do
create_wormhole_details(wh_type, classes_by_id)
else
create_fallback_wormhole_details(static_name)
end
end)
end
# Helper function to create detailed wormhole information
defp create_wormhole_details(wh_type, classes_by_id) do
# Get destination class info
dest_class = Map.get(classes_by_id, wh_type.dest)
# Create enhanced static info
%{
name: wh_type.name,
destination: %{
id: to_string(wh_type.dest),
name: (if dest_class, do: dest_class.title, else: wh_type.dest),
short_name: (if dest_class, do: dest_class.short_name, else: wh_type.dest)
},
properties: %{
lifetime: wh_type.lifetime,
max_mass: wh_type.total_mass,
max_jump_mass: wh_type.max_mass_per_jump,
mass_regeneration: wh_type.mass_regen
}
}
end
# Helper function to create fallback information
defp create_fallback_wormhole_details(static_name) do
%{
name: static_name,
destination: %{
id: nil,
name: "Unknown",
short_name: "?"
},
properties: %{
lifetime: nil,
max_mass: nil,
max_jump_mass: nil,
mass_regeneration: nil
}
}
end
end
describe "show_system_static/1" do
test "returns system static info for a high-sec system" do
params = %{"id" => "30000142"}
result = MockCommonAPIController.show_system_static(params)
assert {:ok, %{data: data}} = result
assert data.solar_system_id == 30000142
assert data.solar_system_name == "Jita"
assert data.region_name == "The Forge"
assert data.security == "0.9"
assert data.type_description == "High Security"
refute Map.has_key?(data, :static_details)
end
test "returns system static info with static details for a wormhole system" do
params = %{"id" => "31000005"}
result = MockCommonAPIController.show_system_static(params)
assert {:ok, %{data: data}} = result
assert data.solar_system_id == 31000005
assert data.solar_system_name == "J123456"
assert data.region_name == "Wormhole Space"
assert data.system_class == 1
assert data.security == "-0.9"
assert data.type_description == "Wormhole"
assert data.effect_name == "Wolf-Rayet Star"
# Check static details
assert Map.has_key?(data, :static_details)
assert length(data.static_details) == 1
static = List.first(data.static_details)
assert static.name == "N110"
assert static.destination.id == "1"
assert static.destination.name == "Class 1 Wormhole"
assert static.destination.short_name == "C1"
assert static.properties.lifetime == "16h"
assert static.properties.max_mass == 500000000
end
test "returns error when system is not found" do
params = %{"id" => "99999999"}
result = MockCommonAPIController.show_system_static(params)
assert {:error, :not_found, "System not found"} = result
end
test "returns error when system_id is not provided" do
params = %{}
result = MockCommonAPIController.show_system_static(params)
assert {:error, :bad_request, message} = result
assert message == "Missing required param: id"
end
test "returns error when system_id is not a valid integer" do
params = %{"id" => "not-an-integer"}
result = MockCommonAPIController.show_system_static(params)
assert {:error, :bad_request, message} = result
assert message =~ "Invalid integer for param id"
end
end
end

View File

@@ -0,0 +1,213 @@
# Standalone test for the MapAPIController
#
# This file can be run directly with:
# elixir test/standalone/map_api_controller_test.exs
#
# It doesn't require any database connections or external dependencies.
# Start ExUnit
ExUnit.start()
defmodule MapAPIControllerTest do
use ExUnit.Case
# Mock modules to simulate the behavior of the controller's dependencies
defmodule MockUtil do
def require_param(params, key) do
case params[key] do
nil -> {:error, "Missing required param: #{key}"}
"" -> {:error, "Param #{key} cannot be empty"}
val -> {:ok, val}
end
end
def parse_int(str) do
case Integer.parse(str) do
{num, ""} -> {:ok, num}
_ -> {:error, "Invalid integer for param id=#{str}"}
end
end
def fetch_map_id(params) do
cond do
params["map_id"] ->
case parse_int(params["map_id"]) do
{:ok, map_id} -> {:ok, map_id}
{:error, _} -> {:error, "Invalid map_id format"}
end
params["slug"] ->
# In a real app, this would look up the map by slug
# For testing, we'll just use a simple mapping
case params["slug"] do
"test-map" -> {:ok, 1}
"another-map" -> {:ok, 2}
_ -> {:error, "Map not found"}
end
true ->
{:error, "Missing required param: map_id or slug"}
end
end
end
defmodule MockMapSystemRepo do
def get_visible_systems_by_map_id(1) do
[
%{id: 30000142, name: "Jita", security: 0.9, region_id: 10000002},
%{id: 30002659, name: "Dodixie", security: 0.9, region_id: 10000032},
%{id: 30002187, name: "Amarr", security: 1.0, region_id: 10000043}
]
end
def get_visible_systems_by_map_id(_) do
[]
end
def get_system_by_id(1, 30000142) do
%{id: 30000142, name: "Jita", security: 0.9, region_id: 10000002}
end
def get_system_by_id(1, 30002659) do
%{id: 30002659, name: "Dodixie", security: 0.9, region_id: 10000032}
end
def get_system_by_id(1, 30002187) do
%{id: 30002187, name: "Amarr", security: 1.0, region_id: 10000043}
end
def get_system_by_id(_, _) do
nil
end
end
defmodule MockMapSolarSystem do
def get_name_by_id(30000142), do: "Jita"
def get_name_by_id(30002659), do: "Dodixie"
def get_name_by_id(30002187), do: "Amarr"
def get_name_by_id(_), do: nil
end
# Mock controller that uses our mock dependencies
defmodule MockMapAPIController do
# Simplified version of list_systems from MapAPIController
def list_systems(params) do
with {:ok, map_id} <- MockUtil.fetch_map_id(params) do
systems = MockMapSystemRepo.get_visible_systems_by_map_id(map_id)
if systems == [] do
{:error, :not_found, "No systems found for this map"}
else
# Format the response
formatted_systems = Enum.map(systems, fn system ->
%{
id: system.id,
name: system.name,
security: system.security
}
end)
{:ok, %{data: formatted_systems}}
end
else
{:error, msg} ->
{:error, :bad_request, msg}
end
end
# Simplified version of show_system from MapAPIController
def show_system(params) do
with {:ok, map_id} <- MockUtil.fetch_map_id(params),
{:ok, system_id_str} <- MockUtil.require_param(params, "id"),
{:ok, system_id} <- MockUtil.parse_int(system_id_str) do
system = MockMapSystemRepo.get_system_by_id(map_id, system_id)
if system == nil do
{:error, :not_found, "System not found"}
else
# Format the response
formatted_system = %{
id: system.id,
name: system.name,
security: system.security
}
{:ok, %{data: formatted_system}}
end
else
{:error, msg} ->
{:error, :bad_request, msg}
end
end
end
describe "list_systems/1" do
test "returns systems with valid map_id" do
params = %{"map_id" => "1"}
result = MockMapAPIController.list_systems(params)
assert {:ok, %{data: data}} = result
assert length(data) == 3
# Check that the data contains the expected systems
jita = Enum.find(data, fn system -> system.id == 30000142 end)
assert jita.name == "Jita"
assert jita.security == 0.9
dodixie = Enum.find(data, fn system -> system.id == 30002659 end)
assert dodixie.name == "Dodixie"
assert dodixie.security == 0.9
end
test "returns systems with valid slug" do
params = %{"slug" => "test-map"}
result = MockMapAPIController.list_systems(params)
assert {:ok, %{data: data}} = result
assert length(data) == 3
end
test "returns error when no systems found" do
params = %{"map_id" => "2"}
result = MockMapAPIController.list_systems(params)
assert {:error, :not_found, message} = result
assert message == "No systems found for this map"
end
test "returns error when map_id is missing" do
params = %{}
result = MockMapAPIController.list_systems(params)
assert {:error, :bad_request, message} = result
assert message == "Missing required param: map_id or slug"
end
test "returns error when invalid map_id is provided" do
params = %{"slug" => "non-existent-map"}
result = MockMapAPIController.list_systems(params)
assert {:error, :bad_request, message} = result
assert message == "Map not found"
end
end
describe "show_system/1" do
test "returns system with valid parameters" do
params = %{"map_id" => "1", "id" => "30000142"}
result = MockMapAPIController.show_system(params)
assert {:ok, %{data: data}} = result
assert data.id == 30000142
assert data.name == "Jita"
assert data.security == 0.9
end
test "returns error when system is not found" do
params = %{"map_id" => "1", "id" => "99999999"}
result = MockMapAPIController.show_system(params)
assert {:error, :not_found, message} = result
assert message == "System not found"
end
end
end

View File

@@ -0,0 +1,321 @@
# Standalone test for the MapAPIController route functionality
#
# This file can be run directly with:
# elixir test/standalone/map_route_api_controller_test.exs
#
# It doesn't require any database connections or external dependencies.
# Start ExUnit
ExUnit.start()
defmodule MapRouteAPIControllerTest do
use ExUnit.Case
# Mock modules to simulate the behavior of the controller's dependencies
defmodule MockUtil do
def require_param(params, key) do
case params[key] do
nil -> {:error, "Missing required param: #{key}"}
"" -> {:error, "Param #{key} cannot be empty"}
val -> {:ok, val}
end
end
def parse_int(str) do
case Integer.parse(str) do
{num, ""} -> {:ok, num}
_ -> {:error, "Invalid integer for param id=#{str}"}
end
end
def fetch_map_id(params) do
cond do
params["map_id"] ->
case parse_int(params["map_id"]) do
{:ok, map_id} -> {:ok, map_id}
{:error, _} -> {:error, "Invalid map_id format"}
end
params["slug"] ->
# In a real app, this would look up the map by slug
# For testing, we'll just use a simple mapping
case params["slug"] do
"test-map" -> {:ok, 1}
"another-map" -> {:ok, 2}
_ -> {:error, "Map not found"}
end
true ->
{:error, "Missing required param: map_id or slug"}
end
end
end
defmodule MockMapSystemRepo do
# Mock data for systems
def get_systems_by_ids(map_id, system_ids) when map_id == 1 do
systems = %{
30000142 => %{id: 30000142, name: "Jita", security: 0.9, region_id: 10000002},
30002659 => %{id: 30002659, name: "Dodixie", security: 0.9, region_id: 10000032},
30002187 => %{id: 30002187, name: "Amarr", security: 1.0, region_id: 10000043}
}
Enum.map(system_ids, fn id -> Map.get(systems, id) end)
|> Enum.filter(&(&1 != nil))
end
def get_systems_by_ids(_, _), do: []
# Mock data for connections
def get_connections_between(map_id, _system_ids) when map_id == 1 do
[
%{source_id: 30000142, target_id: 30002659, distance: 15},
%{source_id: 30002659, target_id: 30002187, distance: 12},
%{source_id: 30000142, target_id: 30002187, distance: 20}
]
end
def get_connections_between(_, _), do: []
end
defmodule MockRouteCalculator do
# Simplified route calculator that just returns a predefined route
def calculate_route(systems, _connections, source_id, target_id, _options \\ []) do
cond do
source_id == 30000142 and target_id == 30002187 ->
# Direct route from Jita to Amarr
route = [
Enum.find(systems, fn s -> s.id == 30000142 end),
Enum.find(systems, fn s -> s.id == 30002187 end)
]
{:ok, %{route: route, jumps: 1, distance: 20}}
source_id == 30000142 and target_id == 30002659 ->
# Direct route from Jita to Dodixie
route = [
Enum.find(systems, fn s -> s.id == 30000142 end),
Enum.find(systems, fn s -> s.id == 30002659 end)
]
{:ok, %{route: route, jumps: 1, distance: 15}}
source_id == 30002659 and target_id == 30002187 ->
# Direct route from Dodixie to Amarr
route = [
Enum.find(systems, fn s -> s.id == 30002659 end),
Enum.find(systems, fn s -> s.id == 30002187 end)
]
{:ok, %{route: route, jumps: 1, distance: 12}}
source_id == 30002659 and target_id == 30000142 ->
# Direct route from Dodixie to Jita
route = [
Enum.find(systems, fn s -> s.id == 30002659 end),
Enum.find(systems, fn s -> s.id == 30000142 end)
]
{:ok, %{route: route, jumps: 1, distance: 15}}
true ->
{:error, "No route found"}
end
end
end
# Mock controller that uses our mock dependencies
defmodule MockMapAPIController do
# Simplified version of calculate_route from MapAPIController
def calculate_route(params) do
with {:ok, map_id} <- MockUtil.fetch_map_id(params),
{:ok, source_id_str} <- MockUtil.require_param(params, "source"),
{:ok, source_id} <- MockUtil.parse_int(source_id_str),
{:ok, target_id_str} <- MockUtil.require_param(params, "target"),
{:ok, target_id} <- MockUtil.parse_int(target_id_str) do
# Get the systems involved in the route
systems = MockMapSystemRepo.get_systems_by_ids(map_id, [source_id, target_id])
# Check if both systems exist
source_system = Enum.find(systems, fn s -> s.id == source_id end)
target_system = Enum.find(systems, fn s -> s.id == target_id end)
if source_system == nil do
{:error, :not_found, "Source system not found"}
else
if target_system == nil do
{:error, :not_found, "Target system not found"}
else
# Get connections between systems
connections = MockMapSystemRepo.get_connections_between(map_id, [source_id, target_id])
# Calculate the route
case MockRouteCalculator.calculate_route(systems, connections, source_id, target_id) do
{:ok, route_data} ->
# Format the response
formatted_route = format_route_response(route_data)
{:ok, %{data: formatted_route}}
{:error, reason} ->
{:error, :not_found, reason}
end
end
end
else
{:error, msg} ->
{:error, :bad_request, msg}
end
end
# Helper function to format the route response
defp format_route_response(route_data) do
%{
route: Enum.map(route_data.route, fn system ->
%{
id: system.id,
name: system.name,
security: system.security
}
end),
jumps: route_data.jumps,
distance: route_data.distance
}
end
end
describe "calculate_route/1" do
test "calculates route between two systems successfully" do
params = %{
"map_id" => "1",
"source" => "30000142", # Jita
"target" => "30002187" # Amarr
}
result = MockMapAPIController.calculate_route(params)
assert {:ok, %{data: data}} = result
assert length(data.route) == 2
assert Enum.at(data.route, 0).id == 30000142
assert Enum.at(data.route, 0).name == "Jita"
assert Enum.at(data.route, 1).id == 30002187
assert Enum.at(data.route, 1).name == "Amarr"
assert data.jumps == 1
assert data.distance == 20
end
test "calculates route using map slug" do
params = %{
"slug" => "test-map",
"source" => "30000142", # Jita
"target" => "30002659" # Dodixie
}
result = MockMapAPIController.calculate_route(params)
assert {:ok, %{data: data}} = result
assert length(data.route) == 2
assert Enum.at(data.route, 0).id == 30000142
assert Enum.at(data.route, 0).name == "Jita"
assert Enum.at(data.route, 1).id == 30002659
assert Enum.at(data.route, 1).name == "Dodixie"
assert data.jumps == 1
assert data.distance == 15
end
test "returns error when source system is not found" do
params = %{
"map_id" => "1",
"source" => "99999999", # Non-existent system
"target" => "30002187" # Amarr
}
result = MockMapAPIController.calculate_route(params)
assert {:error, :not_found, message} = result
assert message == "Source system not found"
end
test "returns error when target system is not found" do
params = %{
"map_id" => "1",
"source" => "30000142", # Jita
"target" => "99999999" # Non-existent system
}
result = MockMapAPIController.calculate_route(params)
assert {:error, :not_found, message} = result
assert message == "Target system not found"
end
test "returns error when map is not found" do
params = %{
"slug" => "non-existent-map",
"source" => "30000142", # Jita
"target" => "30002187" # Amarr
}
result = MockMapAPIController.calculate_route(params)
assert {:error, :bad_request, message} = result
assert message == "Map not found"
end
test "returns error when source parameter is missing" do
params = %{
"map_id" => "1",
"target" => "30002187" # Amarr
}
result = MockMapAPIController.calculate_route(params)
assert {:error, :bad_request, message} = result
assert message == "Missing required param: source"
end
test "returns error when target parameter is missing" do
params = %{
"map_id" => "1",
"source" => "30000142" # Jita
}
result = MockMapAPIController.calculate_route(params)
assert {:error, :bad_request, message} = result
assert message == "Missing required param: target"
end
test "returns error when map_id and slug are both missing" do
params = %{
"source" => "30000142", # Jita
"target" => "30002187" # Amarr
}
result = MockMapAPIController.calculate_route(params)
assert {:error, :bad_request, message} = result
assert message == "Missing required param: map_id or slug"
end
test "returns error when source is not a valid integer" do
params = %{
"map_id" => "1",
"source" => "not-an-integer",
"target" => "30002187" # Amarr
}
result = MockMapAPIController.calculate_route(params)
assert {:error, :bad_request, message} = result
assert message =~ "Invalid integer for param id"
end
test "returns error when target is not a valid integer" do
params = %{
"map_id" => "1",
"source" => "30000142", # Jita
"target" => "not-an-integer"
}
result = MockMapAPIController.calculate_route(params)
assert {:error, :bad_request, message} = result
assert message =~ "Invalid integer for param id"
end
end
end

View File

@@ -0,0 +1,332 @@
# Test for the check_tracking_consistency function in WandererApp.Character.TrackingUtils
#
# This file can be run directly with:
# elixir test/unit/tracking_consistency_test.exs
#
# It doesn't require any database connections or external dependencies.
# Start ExUnit
ExUnit.start()
defmodule WandererApp.Character.TrackingConsistencyTest do
use ExUnit.Case
require Logger
import ExUnit.CaptureIO
# This is a copy of the function from TrackingUtils
def check_tracking_consistency(tracking_data) do
# Find any characters that are followed but not tracked
inconsistent_characters = Enum.filter(tracking_data, fn data ->
data.followed && !data.tracked
end)
# Log a warning for each inconsistent character
Enum.each(inconsistent_characters, fn data ->
character = data.character
# Use IO.puts instead of Logger to avoid dependencies
eve_id = Map.get(character, :eve_id, "unknown")
name = Map.get(character, :name, "unknown")
IO.puts("WARNING: Inconsistent state detected: Character is followed but not tracked. Character ID: #{eve_id}, Name: #{name}")
end)
# Return the original tracking data
tracking_data
end
describe "check_tracking_consistency/1" do
test "logs a warning when a character is followed but not tracked" do
# Create test data with inconsistent state
tracking_data = [
%{
character: %{eve_id: "test-eve-id", name: "Test Character"},
tracked: false,
followed: true
}
]
# Call the function and capture output
output = capture_io(fn ->
check_tracking_consistency(tracking_data)
end)
# Assert that the warning was logged
assert output =~ "Inconsistent state detected: Character is followed but not tracked"
assert output =~ "test-eve-id"
assert output =~ "Test Character"
end
test "does not log a warning when all followed characters are also tracked" do
# Create test data with consistent state
tracking_data = [
%{
character: %{eve_id: "test-eve-id", name: "Test Character"},
tracked: true,
followed: true
}
]
# Call the function and capture output
output = capture_io(fn ->
check_tracking_consistency(tracking_data)
end)
# Assert that no warning was logged
refute output =~ "Inconsistent state detected"
end
test "does not log a warning when no characters are followed" do
# Create test data with no followed characters
tracking_data = [
%{
character: %{eve_id: "test-eve-id", name: "Test Character"},
tracked: true,
followed: false
}
]
# Call the function and capture output
output = capture_io(fn ->
check_tracking_consistency(tracking_data)
end)
# Assert that no warning was logged
refute output =~ "Inconsistent state detected"
end
test "handles multiple characters with mixed states correctly" do
# Create test data with multiple characters in different states
tracking_data = [
%{
character: %{eve_id: "character-1", name: "Character 1"},
tracked: true,
followed: true
},
%{
character: %{eve_id: "character-2", name: "Character 2"},
tracked: false,
followed: true
},
%{
character: %{eve_id: "character-3", name: "Character 3"},
tracked: true,
followed: false
},
%{
character: %{eve_id: "character-4", name: "Character 4"},
tracked: false,
followed: false
}
]
# Call the function and capture output
output = capture_io(fn ->
check_tracking_consistency(tracking_data)
end)
# Assert that only the inconsistent character triggered a warning
assert output =~ "Inconsistent state detected: Character is followed but not tracked"
assert output =~ "character-2"
assert output =~ "Character 2"
refute output =~ "character-1"
refute output =~ "character-3"
refute output =~ "character-4"
end
test "returns the original tracking data unchanged" do
# Create test data
tracking_data = [
%{
character: %{eve_id: "test-eve-id", name: "Test Character"},
tracked: false,
followed: true
}
]
# Call the function and get the result
result = check_tracking_consistency(tracking_data)
# Assert that the returned data is the same as the input data
assert result == tracking_data
end
test "handles empty tracking data without errors" do
# Create empty tracking data
tracking_data = []
# Call the function and capture output
output = capture_io(fn ->
result = check_tracking_consistency(tracking_data)
# Assert that the returned data is the same as the input data
assert result == tracking_data
end)
# Assert that no warning was logged
refute output =~ "Inconsistent state detected"
end
test "handles multiple inconsistent characters correctly" do
# Create test data with multiple inconsistent characters
tracking_data = [
%{
character: %{eve_id: "character-1", name: "Character 1"},
tracked: false,
followed: true
},
%{
character: %{eve_id: "character-2", name: "Character 2"},
tracked: false,
followed: true
},
%{
character: %{eve_id: "character-3", name: "Character 3"},
tracked: true,
followed: true
}
]
# Call the function and capture output
output = capture_io(fn ->
check_tracking_consistency(tracking_data)
end)
# Assert that warnings were logged for both inconsistent characters
assert output =~ "Character ID: character-1"
assert output =~ "Name: Character 1"
assert output =~ "Character ID: character-2"
assert output =~ "Name: Character 2"
refute output =~ "Character ID: character-3"
end
test "handles characters with missing fields gracefully" do
# Create test data with missing fields
tracking_data = [
%{
character: %{eve_id: "character-1"}, # Missing name
tracked: false,
followed: true
},
%{
character: %{name: "Character 2"}, # Missing eve_id
tracked: false,
followed: true
}
]
# Call the function and capture output
output = capture_io(fn ->
result = check_tracking_consistency(tracking_data)
# Assert that the returned data is the same as the input data
assert result == tracking_data
end)
# Assert that warnings were logged with available information
assert output =~ "Character ID: character-1"
assert output =~ "Name: unknown"
assert output =~ "Character ID: unknown"
assert output =~ "Name: Character 2"
end
test "handles characters with nil tracked or followed values" do
# Create test data with nil values
tracking_data = [
%{
character: %{eve_id: "character-1", name: "Character 1"},
tracked: nil,
followed: true
},
%{
character: %{eve_id: "character-2", name: "Character 2"},
tracked: false,
followed: nil
}
]
# Call the function and capture output
output = capture_io(fn ->
result = check_tracking_consistency(tracking_data)
# Assert that the returned data is the same as the input data
assert result == tracking_data
end)
# Assert that a warning was logged for the first character (nil tracked is treated as false)
assert output =~ "Character ID: character-1"
assert output =~ "Name: Character 1"
# No warning for the second character (nil followed is treated as false)
refute output =~ "Character ID: character-2"
end
test "handles malformed tracking data gracefully" do
# Create malformed tracking data (missing required fields)
tracking_data = [
%{
# Missing character field
tracked: false,
followed: true
}
]
# Call the function and capture output, expecting it to handle errors gracefully
assert_raise(KeyError, fn ->
check_tracking_consistency(tracking_data)
end)
end
end
# Additional tests for edge cases in the filter logic
describe "filter logic in check_tracking_consistency/1" do
test "correctly identifies characters that are followed but not tracked" do
# Create test data with various combinations
tracking_data = [
%{
character: %{eve_id: "char-1", name: "Character 1"},
tracked: false,
followed: true
},
%{
character: %{eve_id: "char-2", name: "Character 2"},
tracked: true,
followed: true
},
%{
character: %{eve_id: "char-3", name: "Character 3"},
tracked: false,
followed: false
},
%{
character: %{eve_id: "char-4", name: "Character 4"},
tracked: true,
followed: false
}
]
# Extract the filter logic from the function
inconsistent_characters = Enum.filter(tracking_data, fn data ->
data.followed && !data.tracked
end)
# Assert that only the first character is identified as inconsistent
assert length(inconsistent_characters) == 1
assert hd(inconsistent_characters).character.eve_id == "char-1"
end
test "handles boolean-like values correctly in filter logic" do
# Create test data with various boolean-like values
tracking_data = [
%{
character: %{eve_id: "char-1", name: "Character 1"},
tracked: false,
followed: "true" # String instead of boolean - in Elixir, only false and nil are falsy
}
]
# Extract the filter logic from the function
inconsistent_characters = Enum.filter(tracking_data, fn data ->
data.followed && !data.tracked
end)
# Assert that the character is identified as inconsistent
# (since in Elixir, only false and nil are falsy, everything else is truthy)
assert length(inconsistent_characters) == 1
end
end
end

View File

@@ -0,0 +1,143 @@
# Standalone test for the UtilAPIController
#
# This file can be run directly with:
# elixir test/standalone/util_api_controller_test.exs
#
# It doesn't require any database connections or external dependencies.
# Start ExUnit
ExUnit.start()
defmodule UtilAPIControllerTest do
use ExUnit.Case
# Mock controller that implements the functions we want to test
defmodule MockUtilAPIController do
# Simplified version of fetch_map_id from UtilAPIController
def fetch_map_id(params) do
cond do
params["map_id"] ->
case Integer.parse(params["map_id"]) do
{map_id, ""} -> {:ok, map_id}
_ -> {:error, "Invalid map_id format"}
end
params["slug"] ->
# In a real app, this would look up the map by slug
# For testing, we'll just use a simple mapping
case params["slug"] do
"test-map" -> {:ok, 1}
"another-map" -> {:ok, 2}
_ -> {:error, "Map not found"}
end
true ->
{:error, "Missing required param: map_id or slug"}
end
end
# Simplified version of require_param from UtilAPIController
def require_param(params, key) do
case params[key] do
nil -> {:error, "Missing required param: #{key}"}
"" -> {:error, "Param #{key} cannot be empty"}
val -> {:ok, val}
end
end
# Simplified version of parse_int from UtilAPIController
def parse_int(str) do
case Integer.parse(str) do
{num, ""} -> {:ok, num}
_ -> {:error, "Invalid integer for param id=#{str}"}
end
end
end
describe "fetch_map_id/1" do
test "returns map_id when valid map_id is provided" do
params = %{"map_id" => "123"}
result = MockUtilAPIController.fetch_map_id(params)
assert {:ok, 123} = result
end
test "returns map_id when valid slug is provided" do
params = %{"slug" => "test-map"}
result = MockUtilAPIController.fetch_map_id(params)
assert {:ok, 1} = result
end
test "returns error when map_id is invalid format" do
params = %{"map_id" => "not-a-number"}
result = MockUtilAPIController.fetch_map_id(params)
assert {:error, "Invalid map_id format"} = result
end
test "returns error when slug is not found" do
params = %{"slug" => "non-existent-map"}
result = MockUtilAPIController.fetch_map_id(params)
assert {:error, "Map not found"} = result
end
test "returns error when neither map_id nor slug is provided" do
params = %{}
result = MockUtilAPIController.fetch_map_id(params)
assert {:error, "Missing required param: map_id or slug"} = result
end
test "prioritizes map_id over slug when both are provided" do
params = %{"map_id" => "123", "slug" => "test-map"}
result = MockUtilAPIController.fetch_map_id(params)
assert {:ok, 123} = result
end
end
describe "require_param/2" do
test "returns value when param exists" do
params = %{"key" => "value"}
result = MockUtilAPIController.require_param(params, "key")
assert {:ok, "value"} = result
end
test "returns error when param is missing" do
params = %{}
result = MockUtilAPIController.require_param(params, "key")
assert {:error, "Missing required param: key"} = result
end
test "returns error when param is empty string" do
params = %{"key" => ""}
result = MockUtilAPIController.require_param(params, "key")
assert {:error, "Param key cannot be empty"} = result
end
end
describe "parse_int/1" do
test "returns integer when string is valid integer" do
result = MockUtilAPIController.parse_int("123")
assert {:ok, 123} = result
end
test "returns error when string is not a valid integer" do
result = MockUtilAPIController.parse_int("not-an-integer")
assert {:error, message} = result
assert message =~ "Invalid integer for param id"
end
test "returns error when string contains integer with extra characters" do
result = MockUtilAPIController.parse_int("123abc")
assert {:error, message} = result
assert message =~ "Invalid integer for param id"
end
end
end