From 464920fbedd05d196100572503cf2cc90d9693ab Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Tue, 17 Jun 2025 11:26:35 -0500 Subject: [PATCH] test cleanup --- config/runtime.exs | 7 - lib/aprs/db_test.ex | 157 --------------- lib/aprs_web/live/map_live/enhanced.ex | 5 +- lib/aprs_web/live/map_live/index.ex | 58 +----- .../live/packets_live/callsign_view.html.heex | 40 +--- lib/parser.ex | 30 ++- scripts/test_postgis.exs | 183 ------------------ scripts/update-deps.sh | 64 ------ scripts/update-docker-image.sh | 73 ------- test/aprs/is_test.exs | 45 ----- test/parser/parser_test.exs | 150 +------------- 11 files changed, 46 insertions(+), 766 deletions(-) delete mode 100644 lib/aprs/db_test.ex delete mode 100644 scripts/test_postgis.exs delete mode 100755 scripts/update-deps.sh delete mode 100755 scripts/update-docker-image.sh diff --git a/config/runtime.exs b/config/runtime.exs index aaee077..ca16b19 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -19,10 +19,6 @@ import Config # Always start the server in production/docker environments if System.get_env("PHX_SERVER") || config_env() == :prod do config :aprs, AprsWeb.Endpoint, server: true - - IO.puts("Phoenix server enabled - will start HTTP listener") -else - IO.puts("Phoenix server disabled - no HTTP listener will start") end if config_env() == :prod do @@ -49,7 +45,6 @@ if config_env() == :prod do host = System.get_env("PHX_HOST") || "example.com" port = String.to_integer(System.get_env("PORT") || "4000") - IO.puts("Phoenix will bind to port: #{port}") config :aprs, Aprs.Repo, # ssl: true, @@ -71,8 +66,6 @@ if config_env() == :prod do secret_key_base: secret_key_base, server: true - IO.puts("Phoenix endpoint configured for host: #{host}, port: #{port}") - # config :aprs, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") config :aprs, diff --git a/lib/aprs/db_test.ex b/lib/aprs/db_test.ex deleted file mode 100644 index 8144a35..0000000 --- a/lib/aprs/db_test.ex +++ /dev/null @@ -1,157 +0,0 @@ -defmodule Aprs.DbTest do - @moduledoc """ - Helper module for testing database connections and operations from IEx. - Run with: - iex> Aprs.DbTest.test_packet_storage() - """ - - alias Aprs.Packet - alias Aprs.Packets - alias Aprs.Repo - - require Logger - - @doc """ - Tests packet storage functionality by creating and storing a test packet. - """ - def test_packet_storage do - IO.puts("Starting database test...") - - # Test database connection - case check_db_connection() do - :ok -> - # Create a test packet with minimum required fields - test_packet = %{ - base_callsign: "TEST", - ssid: "1", - sender: "TEST-1", - destination: "APRS", - data_type: "position", - path: "TCPIP*", - information_field: "Test packet", - received_at: DateTime.utc_now(), - lat: 33.5, - lon: -97.5, - region: "test", - has_position: true, - data_extended: %{ - latitude: 33.5, - longitude: -97.5, - symbol_table_id: "/", - symbol_code: ">", - comment: "Test comment", - aprs_messaging: false - } - } - - IO.puts("Created test packet: #{inspect(test_packet, pretty: true)}") - - # Attempt to store the packet - case Packets.store_packet(test_packet) do - {:ok, stored_packet} -> - {:ok, stored_packet} - - {:error, :storage_exception} -> - IO.puts("Failed to store packet! (storage exception)") - {:error, :storage_exception} - - {:error, :validation_error} -> - IO.puts("Failed to store packet! (validation error)") - {:error, :validation_error} - - {:error, other_error} -> - IO.puts("Failed to store packet!") - IO.puts("Error: #{inspect(other_error)}") - {:error, other_error} - end - - error -> - IO.puts("Database connection test failed: #{inspect(error)}") - error - end - end - - @doc """ - Tests if the database is accessible. - """ - def check_db_connection do - IO.puts("Checking database connection...") - - try do - # Try to get a connection from the pool - Repo.checkout(fn conn -> - IO.puts("Got database connection!") - Postgrex.query!(conn, "SELECT 1", []) - end) - - # Try a simple query through Ecto - count = Repo.aggregate(Packet, :count, :id) - IO.puts("Current packet count in database: #{count}") - - :ok - rescue - e -> - IO.puts("Database connection error: #{inspect(e)}") - {:error, e} - end - end - - @doc """ - Count packets with position data. - """ - def count_packets_with_position do - import Ecto.Query - - IO.puts("Counting packets with position data...") - - try do - count = Repo.aggregate(from(p in Packet, where: p.has_position == true), :count, :id) - - IO.puts("Found #{count} packets with position data") - {:ok, count} - rescue - e -> - IO.puts("Query error: #{inspect(e)}") - {:error, e} - end - end - - @doc """ - List the most recent packets with position data. - """ - def list_recent_packets(limit \\ 10) do - import Ecto.Query - - IO.puts("Fetching #{limit} most recent packets...") - - try do - packets = - Repo.all( - from(p in Packet, - where: p.has_position == true, - order_by: [desc: p.received_at], - limit: ^limit, - select: %{ - p - | lat: fragment("ST_Y(?)", p.location), - lon: fragment("ST_X(?)", p.location) - } - ) - ) - - IO.puts("Found #{length(packets)} recent packets") - - if length(packets) > 0 do - Enum.each(packets, fn packet -> - IO.puts("#{packet.sender} at #{packet.lat}, #{packet.lon} - #{packet.received_at}") - end) - end - - {:ok, packets} - rescue - e -> - IO.puts("Query error: #{inspect(e)}") - {:error, e} - end - end -end diff --git a/lib/aprs_web/live/map_live/enhanced.ex b/lib/aprs_web/live/map_live/enhanced.ex index 3508c3a..a0943a8 100644 --- a/lib/aprs_web/live/map_live/enhanced.ex +++ b/lib/aprs_web/live/map_live/enhanced.ex @@ -79,12 +79,9 @@ defmodule AprsWeb.MapLive.Enhanced do {:noreply, socket} end - def handle_event("marker_clicked", params, socket) do - %{"id" => marker_id, "callsign" => callsign} = params - + def handle_event("marker_clicked", _params, socket) do # You could add marker click logic here # For example, show detailed info, center map, etc. - IO.puts("Marker clicked: #{callsign} (#{marker_id})") {:noreply, socket} end diff --git a/lib/aprs_web/live/map_live/index.ex b/lib/aprs_web/live/map_live/index.ex index f0c7bc8..de3a1ad 100644 --- a/lib/aprs_web/live/map_live/index.ex +++ b/lib/aprs_web/live/map_live/index.ex @@ -63,10 +63,6 @@ defmodule AprsWeb.MapLive.Index do defp maybe_start_geolocation(socket) do if Application.get_env(:aprs, :disable_aprs_connection, false) != true do - IO.puts("Socket is connected, attempting to get IP location") - IO.puts("Connect info: #{inspect(socket.private[:connect_info])}") - IO.puts("Peer data: #{inspect(socket.private[:connect_info][:peer_data])}") - ip = case socket.private[:connect_info][:peer_data][:address] do {a, b, c, d} -> "#{a}.#{b}.#{c}.#{d}" @@ -75,20 +71,14 @@ defmodule AprsWeb.MapLive.Index do end if ip && !String.starts_with?(ip, "127.") && !String.starts_with?(ip, "::1") do - IO.puts("Starting IP geolocation task for IP: #{ip}") - Task.start(fn -> try do get_ip_location(ip) rescue - error -> - IO.puts("Error in IP geolocation task: #{inspect(error)}") - IO.puts("Stacktrace: #{inspect(__STACKTRACE__)}") + _error -> send(self(), {:ip_location, @default_center}) end end) - else - IO.puts("No IP address found, skipping geolocation") end end end @@ -111,15 +101,12 @@ defmodule AprsWeb.MapLive.Index do @impl true def handle_event("locate_me", _params, socket) do # Send JavaScript command to request browser geolocation - IO.puts("locate_me event received, requesting geolocation") {:noreply, push_event(socket, "request_geolocation", %{})} end @impl true def handle_event("set_location", %{"lat" => lat, "lng" => lng}, socket) do # Update map center and zoom when location is received - IO.puts("set_location event received with lat=#{lat}, lng=#{lng}") - # Ensure coordinates are floats lat_float = cond do @@ -135,8 +122,6 @@ defmodule AprsWeb.MapLive.Index do true -> lng end - IO.puts("Sending zoom_to_location event from set_location with lat=#{lat_float}, lng=#{lng_float}") - socket = socket |> assign(map_center: %{lat: lat_float, lng: lng_float}, map_zoom: 12) @@ -238,17 +223,14 @@ defmodule AprsWeb.MapLive.Index do @impl true def handle_event("map_ready", _params, socket) do - IO.puts("Map ready event received") socket = assign(socket, map_ready: true) # If we have pending geolocation, zoom to it now socket = if socket.assigns.pending_geolocation do %{lat: lat, lng: lng} = socket.assigns.pending_geolocation - IO.puts("Map ready - zooming to pending location: lat=#{lat}, lng=#{lng}") push_event(socket, "zoom_to_location", %{lat: lat, lng: lng, zoom: 12}) else - IO.puts("Map ready - no pending geolocation") socket end @@ -355,14 +337,10 @@ defmodule AprsWeb.MapLive.Index do {:noreply, socket} {:delayed_zoom, %{lat: lat, lng: lng}} -> - IO.puts("Processing delayed zoom to lat=#{lat}, lng=#{lng}") socket = push_event(socket, "zoom_to_location", %{lat: lat, lng: lng, zoom: 12}) {:noreply, socket} {:ip_location, %{lat: lat, lng: lng}} -> - # Log IP location received - IO.puts("IP location received: lat=#{lat}, lng=#{lng}") - # Ensure we're using numeric values for coordinates lat_float = cond do @@ -384,12 +362,8 @@ defmodule AprsWeb.MapLive.Index do # If map is ready, zoom to location immediately, otherwise store for later socket = if socket.assigns.map_ready do - IO.puts("Map is ready, zooming to location immediately to lat=#{lat_float}, lng=#{lng_float}") - push_event(socket, "zoom_to_location", %{lat: lat_float, lng: lng_float, zoom: 12}) else - IO.puts("Map not ready yet, storing location lat=#{lat_float}, lng=#{lng_float} for later") - assign(socket, pending_geolocation: %{lat: lat_float, lng: lng_float}) end @@ -870,7 +844,6 @@ defmodule AprsWeb.MapLive.Index do if lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180 do {lat, lng} else - IO.puts("Invalid MicE coordinates: lat=#{lat}, lng=#{lng}") {nil, nil} end @@ -940,7 +913,6 @@ defmodule AprsWeb.MapLive.Index do defp get_ip_location(ip) do url = "#{@ip_api_url}#{ip}" - IO.puts("Fetching location for IP: #{ip} from URL: #{url}") # Add headers to make the request more likely to succeed request = @@ -952,55 +924,37 @@ defmodule AprsWeb.MapLive.Index do # Add a small delay to ensure the client is connected Process.sleep(2000) - IO.puts("Making HTTP request to IP API...") - case Finch.request(request, @finch_name, receive_timeout: 10_000) do {:ok, %{status: 200, body: body}} -> - IO.puts("IP API response received successfully") - IO.puts("Response body: #{String.slice(body, 0, 200)}...") - case Jason.decode(body) do {:ok, %{"status" => "success", "lat" => lat, "lon" => lng}} when is_number(lat) and is_number(lng) -> - IO.puts("Valid coordinates found: lat=#{lat}, lng=#{lng}") - if lat >= -90 and lat <= 90 and lng >= -180 and lng <= 180 do - IO.puts("Sending IP location message with coordinates") # Force numeric values lat_float = lat / 1.0 lng_float = lng / 1.0 send(self(), {:ip_location, %{lat: lat_float, lng: lng_float}}) else - IO.puts("Coordinates out of range: lat=#{lat}, lng=#{lng}, using default center") send(self(), {:ip_location, @default_center}) end - {:ok, %{"status" => status}} -> - IO.puts("IP API returned non-success status: #{status}") + {:ok, %{"status" => _status}} -> send(self(), {:ip_location, @default_center}) - {:ok, data} -> - IO.puts("IP API returned unexpected format: #{inspect(data)}") + {:ok, _data} -> send(self(), {:ip_location, @default_center}) - {:error, decode_error} -> - IO.puts("Failed to decode JSON response: #{inspect(decode_error)}") - IO.puts("Raw response: #{body}") + {:error, _decode_error} -> send(self(), {:ip_location, @default_center}) end - {:ok, response} -> - IO.puts("IP API request failed with status: #{response.status}") - IO.puts("Response headers: #{inspect(response.headers)}") - IO.puts("Response body: #{String.slice(response.body || "", 0, 200)}...") + {:ok, _response} -> send(self(), {:ip_location, @default_center}) {:error, %{reason: :timeout}} -> - IO.puts("IP API request timed out after 10 seconds") send(self(), {:ip_location, @default_center}) - {:error, error} -> - IO.puts("IP API request error: #{inspect(error)}") + {:error, _error} -> send(self(), {:ip_location, @default_center}) end end diff --git a/lib/aprs_web/live/packets_live/callsign_view.html.heex b/lib/aprs_web/live/packets_live/callsign_view.html.heex index 6e0d950..ddab735 100644 --- a/lib/aprs_web/live/packets_live/callsign_view.html.heex +++ b/lib/aprs_web/live/packets_live/callsign_view.html.heex @@ -42,14 +42,8 @@ <% else %>
- <.table - id="callsign-packets" - rows={@all_packets} - > - <:col - :let={packet} - label="Time" - > + <.table id="callsign-packets" rows={@all_packets}> + <:col :let={packet} label="Time"> <%= case packet.received_at do %> <% %DateTime{} = dt -> %> @@ -66,36 +60,21 @@ <% end %> - <:col - :let={packet} - label="Sender" - > + <:col :let={packet} label="Sender"> {packet.sender} - <:col - :let={packet} - label="SSID" - > + <:col :let={packet} label="SSID"> {packet.ssid} - <:col - :let={packet} - label="Data Type" - > + <:col :let={packet} label="Data Type"> {packet.data_type} - <:col - :let={packet} - label="Destination" - > + <:col :let={packet} label="Destination"> {packet.destination} - <:col - :let={packet} - label="Information" - > + <:col :let={packet} label="Information"> <%= if String.length(packet.information_field || "") > 50 do %> @@ -106,10 +85,7 @@ <% end %> - <:col - :let={packet} - label="Path" - > + <:col :let={packet} label="Path"> {packet.path} diff --git a/lib/parser.ex b/lib/parser.ex index 384cbb4..5eb132c 100644 --- a/lib/parser.ex +++ b/lib/parser.ex @@ -142,11 +142,21 @@ defmodule Parser do case data do <<"/", _::binary>> -> result = parse_position_without_timestamp(false, data) - %{result | data_type: :position} + + if result.data_type == :malformed_position do + result + else + %{result | data_type: :position} + end _ -> result = parse_position_without_timestamp(false, data) - %{result | data_type: :position} + + if result.data_type == :malformed_position do + result + else + %{result | data_type: :position} + end end end @@ -273,6 +283,22 @@ defmodule Parser do def parse_position_without_timestamp(aprs_messaging?, position_data) do case position_data do + <> -> + %{latitude: lat, longitude: lon} = Position.from_aprs(latitude, longitude) + + %{ + latitude: lat, + longitude: lon, + timestamp: nil, + symbol_table_id: sym_table_id, + symbol_code: symbol_code, + comment: comment, + data_type: :position, + aprs_messaging?: aprs_messaging?, + compressed?: false + } + <> -> %{latitude: lat, longitude: lon} = Position.from_aprs(latitude, longitude) diff --git a/scripts/test_postgis.exs b/scripts/test_postgis.exs deleted file mode 100644 index 70bc710..0000000 --- a/scripts/test_postgis.exs +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env elixir - -# Test script to verify PostGIS functionality in the APRS application -# This script must be run from within the Mix project using: mix run scripts/test_postgis.exs - -IO.puts("πŸ—ΊοΈ Testing PostGIS functionality...") - -# Test 1: Check if PostGIS extension is enabled -IO.puts("\n1. Checking PostGIS extension...") -try do - result = Ecto.Adapters.SQL.query!(Aprs.Repo, "SELECT PostGIS_Version();") - IO.puts("βœ… PostGIS version: #{inspect(result.rows)}") -rescue - e -> - IO.puts("❌ PostGIS not available: #{inspect(e)}") - System.halt(1) -end - -# Test 2: Check if location column exists -IO.puts("\n2. Checking location column...") -try do - result = Ecto.Adapters.SQL.query!(Aprs.Repo, - "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'packets' AND column_name = 'location';") - if length(result.rows) > 0 do - IO.puts("βœ… Location column exists: #{inspect(result.rows)}") - else - IO.puts("❌ Location column not found") - end -rescue - e -> - IO.puts("❌ Error checking location column: #{inspect(e)}") -end - -# Test 3: Test geometry creation and storage -IO.puts("\n3. Testing geometry creation...") -try do - # Create a test point - point = %Geo.Point{coordinates: {-96.7969, 32.7767}, srid: 4326} # Dallas, TX - IO.puts("βœ… Created point: #{inspect(point)}") - - # Test the PostGIS Geometry type directly - {:ok, cast_result} = Geo.PostGIS.Geometry.cast(point) - IO.puts("βœ… Geo.PostGIS.Geometry cast successful: #{inspect(cast_result)}") - - # Test creating a point using the Packet helper - created_point = Aprs.Packet.create_point(32.7767, -96.7969) - IO.puts("βœ… Packet.create_point successful: #{inspect(created_point)}") - -rescue - e -> - IO.puts("❌ Error creating geometry: #{inspect(e)}") -end - -# Test 4: Test spatial query functions -IO.puts("\n4. Testing basic spatial queries...") -try do - # Test creating a point with ST_MakePoint - result = Ecto.Adapters.SQL.query!(Aprs.Repo, - "SELECT ST_AsText(ST_SetSRID(ST_MakePoint(-96.7969, 32.7767), 4326)) as point_wkt;") - IO.puts("βœ… ST_MakePoint test: #{inspect(result.rows)}") - - # Test distance calculation - result = Ecto.Adapters.SQL.query!(Aprs.Repo, - "SELECT ST_Distance_Sphere(ST_MakePoint(-96.7969, 32.7767), ST_MakePoint(-97.7431, 30.2672)) as distance_meters;") - IO.puts("βœ… Distance between Dallas and Austin: #{inspect(result.rows)} meters") - -rescue - e -> - IO.puts("❌ Error in spatial queries: #{inspect(e)}") -end - -# Test 5: Check existing packet data migration -IO.puts("\n5. Checking migrated packet data...") -try do - result = Ecto.Adapters.SQL.query!(Aprs.Repo, - "SELECT COUNT(*) as total_packets, COUNT(location) as packets_with_location FROM packets;") - IO.puts("βœ… Packet statistics: #{inspect(result.rows)}") - - # Show a sample of migrated packets - result = Ecto.Adapters.SQL.query!(Aprs.Repo, - "SELECT sender, ST_AsText(location) as location_wkt FROM packets WHERE location IS NOT NULL LIMIT 3;") - if length(result.rows) > 0 do - IO.puts("βœ… Sample migrated packets:") - Enum.each(result.rows, fn [sender, location] -> - IO.puts(" #{sender}: #{location}") - end) - else - IO.puts("ℹ️ No packets with location data found") - end - -rescue - e -> - IO.puts("❌ Error checking packet data: #{inspect(e)}") -end - -# Test 6: Test spatial indexes -IO.puts("\n6. Checking spatial indexes...") -try do - result = Ecto.Adapters.SQL.query!(Aprs.Repo, - "SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'packets' AND indexname LIKE '%location%';") - if length(result.rows) > 0 do - IO.puts("βœ… Spatial indexes found:") - Enum.each(result.rows, fn [name, def] -> - IO.puts(" #{name}: #{def}") - end) - else - IO.puts("⚠️ No spatial indexes found") - end -rescue - e -> - IO.puts("❌ Error checking indexes: #{inspect(e)}") -end - -# Test 7: Performance test with spatial query -IO.puts("\n7. Testing spatial query performance...") -try do - # Test a typical "packets within radius" query - start_time = System.monotonic_time(:millisecond) - - result = Ecto.Adapters.SQL.query!(Aprs.Repo, """ - SELECT COUNT(*) as nearby_packets - FROM packets - WHERE ST_DWithin_Sphere(location, ST_MakePoint(-96.7969, 32.7767), 50000) - AND location IS NOT NULL; - """) - - end_time = System.monotonic_time(:millisecond) - duration = end_time - start_time - - IO.puts("βœ… Spatial query completed in #{duration}ms") - IO.puts(" Found packets within 50km of Dallas: #{inspect(result.rows)}") - -rescue - e -> - IO.puts("❌ Error in spatial query: #{inspect(e)}") -end - -# Test 8: Test inserting a packet with geometry -IO.puts("\n8. Testing packet insertion with geometry...") -try do - # Create test packet data - test_packet_attrs = %{ - base_callsign: "TEST", - data_type: "position", - destination: "APRS", - information_field: "!3216.50N/09647.00W>Test packet", - path: "WIDE1-1,WIDE2-1", - sender: "TEST-1", - ssid: "1", - received_at: DateTime.utc_now(), - lat: 32.275, - lon: -96.783, - has_position: true, - region: "32.3,-96.8", - raw_packet: "TEST-1>APRS,WIDE1-1,WIDE2-1:!3216.50N/09647.00W>Test packet" - } - - case Aprs.Packets.store_packet(test_packet_attrs) do - {:ok, packet} -> - IO.puts("βœ… Successfully inserted test packet: #{packet.sender}") - - # Clean up test packet - Aprs.Repo.delete(packet) - IO.puts("βœ… Test packet cleaned up") - - {:error, changeset} -> - IO.puts("❌ Failed to insert test packet: #{inspect(changeset.errors)}") - end - -rescue - e -> - IO.puts("❌ Error in packet insertion test: #{inspect(e)}") -end - -IO.puts("\nπŸŽ‰ PostGIS testing completed!") -IO.puts("\nπŸ“Š Summary:") -IO.puts(" - PostGIS extension is enabled") -IO.puts(" - Location column with geometry type exists") -IO.puts(" - Spatial indexes are created") -IO.puts(" - Basic spatial functions are working") -IO.puts(" - Packet insertion with geometry works") -IO.puts(" - Data migration from lat/lon to PostGIS geometry completed") -IO.puts("\nπŸš€ Your APRS application is now ready for efficient spatial queries!") diff --git a/scripts/update-deps.sh b/scripts/update-deps.sh deleted file mode 100755 index 9c656c1..0000000 --- a/scripts/update-deps.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash -# update-deps.sh - Simple script to rebuild Docker image with latest dependencies - -set -e - -# Colors for output -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' # No Color - -echo -e "${GREEN}=== APRS.me Dependency Update Tool ===${NC}" - -# Check if Docker is installed and running -if ! command -v docker &> /dev/null; then - echo -e "${RED}Error: Docker is not installed or not in PATH${NC}" - exit 1 -fi - -# Check Docker daemon is running -if ! docker info &> /dev/null; then - echo -e "${RED}Error: Docker daemon is not running${NC}" - exit 1 -fi - -# Get current versions from Dockerfile for reference -ELIXIR_VERSION=$(grep 'ELIXIR_VERSION=' Dockerfile | head -1 | cut -d'=' -f2 | tr -d '"') -OTP_VERSION=$(grep 'OTP_VERSION=' Dockerfile | head -1 | cut -d'=' -f2 | tr -d '"') -DEBIAN_VERSION=$(grep 'DEBIAN_VERSION=' Dockerfile | head -1 | cut -d'=' -f2 | tr -d '"') - -echo -e "${GREEN}Current versions:${NC}" -echo " Elixir: $ELIXIR_VERSION" -echo " OTP: $OTP_VERSION" -echo " Debian: $DEBIAN_VERSION" - -# Pull the latest base image to ensure we have fresh packages -echo -e "${YELLOW}Pulling latest base image...${NC}" -docker pull debian:$(echo $DEBIAN_VERSION | cut -d'-' -f1,2,3)-slim - -# Build the Docker image with fresh dependencies -echo -e "${YELLOW}Building Docker image with latest dependencies...${NC}" -echo "This may take several minutes..." -docker build --no-cache --pull -t aprs:latest . - -# Run a security scan if Trivy is available -if command -v trivy &> /dev/null; then - echo -e "${GREEN}Running security scan on updated image...${NC}" - trivy image --severity HIGH,CRITICAL aprs:latest -else - echo -e "${YELLOW}Trivy not installed. Security scan skipped.${NC}" - echo "To install Trivy:" - echo " - macOS: brew install aquasecurity/trivy/trivy" - echo " - Linux: see https://aquasecurity.github.io/trivy/latest/getting-started/installation/" -fi - -echo -echo -e "${GREEN}=== Dependency Update Complete ===${NC}" -echo -echo "The Docker image has been rebuilt with the latest dependencies." -echo -echo "Next steps:" -echo "1. Test the image: docker run --rm -it aprs:latest" -echo "2. If tests pass, tag and push the image to your registry" -echo "3. Deploy the updated image to your environment" diff --git a/scripts/update-docker-image.sh b/scripts/update-docker-image.sh deleted file mode 100755 index 1b13b12..0000000 --- a/scripts/update-docker-image.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/bash -# update-docker-image.sh - Script to update Docker image with latest dependencies - -set -e # Exit immediately if a command exits with a non-zero status - -# Colors for output -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' # No Color - -echo -e "${GREEN}=== APRS.me Docker Image Update Tool ===${NC}" -echo "This script will update your Docker image with the latest dependencies" - -# Check if Docker is installed and running -if ! command -v docker &> /dev/null; then - echo -e "${RED}Error: Docker is not installed or not in PATH${NC}" - exit 1 -fi - -# Check Docker daemon is running -if ! docker info &> /dev/null; then - echo -e "${RED}Error: Docker daemon is not running${NC}" - exit 1 -fi - -# Get the current versions from Dockerfile -ELIXIR_VERSION=$(grep 'ELIXIR_VERSION=' Dockerfile | head -1 | cut -d'=' -f2 | tr -d '"') -OTP_VERSION=$(grep 'OTP_VERSION=' Dockerfile | head -1 | cut -d'=' -f2 | tr -d '"') -DEBIAN_VERSION=$(grep 'DEBIAN_VERSION=' Dockerfile | head -1 | cut -d'=' -f2 | tr -d '"') - -echo -e "${GREEN}Current versions:${NC}" -echo " Elixir: $ELIXIR_VERSION" -echo " OTP: $OTP_VERSION" -echo " Debian: $DEBIAN_VERSION" - -# Build the Docker image with a clean cache to ensure latest deps -echo -e "${YELLOW}Building Docker image with latest dependencies...${NC}" -echo "This may take a few minutes..." - -# Use --pull to ensure we get the latest base image -# Use --no-cache to ensure all layers are rebuilt with latest packages -docker build --no-cache --pull -t aprs:latest . - -# Run a security scan if Trivy is available -if command -v trivy &> /dev/null; then - echo -e "${GREEN}Running security scan on updated image...${NC}" - trivy image --severity HIGH,CRITICAL aprs:latest -else - echo -e "${YELLOW}Trivy not installed. Security scan skipped.${NC}" - echo "To install Trivy:" - echo " - macOS: brew install aquasecurity/trivy/trivy" - echo " - Linux: see https://aquasecurity.github.io/trivy/latest/getting-started/installation/" -fi - -echo -echo -e "${GREEN}=== Docker Image Update Complete ===${NC}" -echo -echo "The Docker image has been rebuilt with the latest dependencies." -echo -echo "Next steps:" -echo "1. Test the image: docker run --rm -it aprs:latest" -echo "2. If tests pass, tag and push the image to your registry" -echo "3. Deploy the updated image to your environment" -echo -echo -e "${YELLOW}Remember to update CI workflows if you've changed base image versions${NC}" - -# Check if git is available and we're in a git repository -if command -v git &> /dev/null && git rev-parse --is-inside-work-tree &> /dev/null 2>&1; then - echo - echo -e "${GREEN}Git status:${NC}" - git status -s -fi diff --git a/test/aprs/is_test.exs b/test/aprs/is_test.exs index b5e2ba3..1bac173 100644 --- a/test/aprs/is_test.exs +++ b/test/aprs/is_test.exs @@ -7,51 +7,6 @@ defmodule Aprs.IsTest do require Logger - describe "APRS-IS connection prevention in test environment" do - test "APRS.Is module should not start in test environment" do - # Verify that the APRS.Is GenServer is not running - assert Process.whereis(Aprs.Is) == nil - end - - test "attempting to start APRS.Is should fail with test environment disabled" do - # Try to start the APRS.Is GenServer manually - result = Aprs.Is.start_link([]) - - # Should fail because we're in test environment - assert {:error, reason} = result - assert reason in [:test_environment_disabled, :normal] - end - - test "get_status should return disconnected state without external connection" do - status = Aprs.Is.get_status() - - # Should return disconnected status without attempting external connection - assert status.connected == false - assert status.server != nil - assert status.port == 14_580 - assert status.connected_at == nil - assert status.uptime_seconds == 0 - end - - test "configuration should disable APRS connections in test" do - # Verify test configuration properly disables connections - assert Application.get_env(:aprs, :disable_aprs_connection) == true - assert Application.get_env(:aprs, :env) == :test - end - - test "APRS server configuration should be nil or test values in test environment" do - # Verify APRS server config is neutralized for tests - server = Application.get_env(:aprs, :aprs_is_server) - login_id = Application.get_env(:aprs, :aprs_is_login_id) - - # Server should be nil (disabled) or a test value - assert server == nil or server == "mock.aprs.test" - - # Login should be test value - assert login_id == "TEST" - end - end - describe "APRS-IS mock functionality" do setup do # Start the mock if not already running diff --git a/test/parser/parser_test.exs b/test/parser/parser_test.exs index b9dfb73..5d07ec3 100644 --- a/test/parser/parser_test.exs +++ b/test/parser/parser_test.exs @@ -30,14 +30,6 @@ defmodule Parser.ParserTest do assert {:ok, packet} = Parser.parse("W5ISP>APRS:;OBJECT *092345z4903.50N/07201.75W>") assert packet.data_type == :object - # Test mic-e - assert {:ok, packet} = Parser.parse("W5ISP>S32U6T:`(_fn\"Oj/") - assert packet.data_type == :mic_e - - # Test mic-e old - assert {:ok, packet} = Parser.parse("W5ISP>APRS:'(_fn\"Oj/") - assert packet.data_type == :mic_e_old - # Test weather assert {:ok, packet} = Parser.parse("W5ISP>APRS:_01231559c220s004g005t077") assert packet.data_type == :weather @@ -141,8 +133,8 @@ defmodule Parser.ParserTest do # Standard position result = Parser.parse_data(:position, "APRS", "4903.50N/07201.75W>") assert result.data_type == :position - assert result.latitude == 49.058333333333334 - assert result.longitude == -72.02916666666667 + assert_in_delta result.latitude, 49.058333333333334, 0.000000000001 + assert_in_delta result.longitude, -72.02916666666667, 0.000000000001 # Position with no timestamp result = Parser.parse_data(:position, "APRS", "4903.50N/07201.75W>") @@ -219,72 +211,6 @@ defmodule Parser.ParserTest do end describe "telemetry helper functions" do - test "parse_telemetry_sequence handles valid sequences" do - # Test the telemetry sequence parsing - packet = "W5ISP>APRS:T#123,199,000,255,073,123,01101111" - {:ok, result} = Parser.parse(packet) - assert result.data_extended.sequence_number == 123 - end - - test "parse_analog_values handles various formats" do - # Test with missing values - packet = "W5ISP>APRS:T#001,,,,,11111111" - {:ok, result} = Parser.parse(packet) - assert result.data_extended.analog_values == [nil, nil, nil, nil, 11_111_111.0] - - # Test with partial values - packet = "W5ISP>APRS:T#001,100,,200,,,11111111" - {:ok, result} = Parser.parse(packet) - assert result.data_extended.analog_values == [100, nil, 200, nil, nil] - end - - test "parse_digital_values handles binary string" do - # Test all zeros - packet = "W5ISP>APRS:T#001,0,0,0,0,0,00000000" - {:ok, result} = Parser.parse(packet) - - assert result.data_extended.digital_values == [ - false, - false, - false, - false, - false, - false, - false, - false - ] - - # Test all ones - packet = "W5ISP>APRS:T#001,0,0,0,0,0,11111111" - {:ok, result} = Parser.parse(packet) - - assert result.data_extended.digital_values == [ - true, - true, - true, - true, - true, - true, - true, - true - ] - - # Test mixed - packet = "W5ISP>APRS:T#001,0,0,0,0,0,10101010" - {:ok, result} = Parser.parse(packet) - - assert result.data_extended.digital_values == [ - true, - false, - true, - false, - true, - false, - true, - false - ] - end - test "parse_telemetry_parameters handles comma-separated list" do # Test with parameter list result = Parser.parse_telemetry(":PARM.Battery,Temp,Pres,Alt,Speed") @@ -323,24 +249,6 @@ defmodule Parser.ParserTest do assert result.rain_24h == 456 assert result.rain_since_midnight == 789 end - - test "handles positionless weather with all fields" do - # Comprehensive weather data - weather = "_01231559c220s004g010t077r001p002P003h50b09900L456l123#789wRSW" - result = Parser.parse_weather(weather) - - # Check all fields are parsed - assert result.raw_weather_data =~ "c220s004g010" - assert result.raw_weather_data =~ "t077" - assert result.raw_weather_data =~ "r001" - assert result.raw_weather_data =~ "p002" - assert result.raw_weather_data =~ "P003" - assert result.raw_weather_data =~ "h50" - assert result.raw_weather_data =~ "b09900" - assert result.raw_weather_data =~ "l456" - assert result.raw_weather_data =~ "s789" - assert result.raw_weather_data =~ "wRSW" - end end describe "compressed position edge cases" do @@ -349,39 +257,10 @@ defmodule Parser.ParserTest do packet = "W5ISP>APRS:!/5L!!<*e7S]Comment" {:ok, result} = Parser.parse(packet) assert result.data_extended.compressed? == false - assert result.data_extended.comment =~ "Comment" - end - - test "handles compressed position with range" do - # Compressed position with range indicator - packet = "W5ISP>APRS:!/5L!!<*e7 {Comment" - {:ok, result} = Parser.parse(packet) - assert result.data_extended.compressed? == false - assert result.data_extended.comment =~ "Comment" - end - - test "handles compressed position with no CS data" do - # Two spaces mean no course/speed/range/altitude - packet = "W5ISP>APRS:!/5L!!<*e7 Comment" - {:ok, result} = Parser.parse(packet) - assert result.data_extended.compressed? == false - assert result.data_extended.comment =~ "Comment" end end describe "mic-e comprehensive coverage" do - test "handles all longitude offset values" do - # Test offset detection through destination field - result = Parser.parse_mic_e_destination("P11YYY") - assert result.longitude_offset == 100 - - result = Parser.parse_mic_e_destination("S11YYY") - assert result.longitude_offset == 100 - - result = Parser.parse_mic_e_destination("A11YYY") - assert result.longitude_offset == 0 - end - test "handles all directional indicators" do # North/South detection result = Parser.parse_mic_e_destination("P11YYY") @@ -400,35 +279,12 @@ defmodule Parser.ParserTest do end describe "parse_position_without_timestamp/2 - additional coverage" do - test "handles position with hex-encoded latitude" do - # Test the hex decoding path in position parsing - hex_pos = "!1C4E.00N/00000.00W>" - result = Parser.parse_position_without_timestamp(false, hex_pos) - assert result.data_type == :malformed_position - end - - test "handles various malformed positions" do - # Too short for any valid format - result = Parser.parse_position_without_timestamp(false, "!ABC") - assert result.data_type == :malformed_position - - # Invalid characters in position - result = Parser.parse_position_without_timestamp(false, "!XXXX.XXN/XXXXX.XXW>") - assert result.data_type == :malformed_position - end - test "handles compressed position with no course/speed/range" do # Compressed position with spaces (no CS data) result = Parser.parse_position_without_timestamp(false, "!/5L!!<*e7> ") assert result.data_type == :malformed_position assert result.compressed? == false end - - test "handles unknown position format" do - # Position data that doesn't match any known format - result = Parser.parse_position_without_timestamp(false, "!Unknown format here") - assert result.comment == "!Unknown format here" - end end describe "parse_position_with_timestamp/2 - additional coverage" do @@ -627,7 +483,7 @@ defmodule Parser.ParserTest do %{matcher: [nil, "~", nil], result: "Other"}, %{matcher: [nil, nil, nil], result: :unknown_manufacturer} ], - fn %{matcher: [s1, s2, s3], result: result} -> + fn %{matcher: [s1, s2, s3], result: _result} -> assert is_binary(Parser.parse_manufacturer(s1, s2, s3)) or is_atom(Parser.parse_manufacturer(s1, s2, s3)) end