diff --git a/web/app.rb b/web/app.rb index af2f6b2..011724f 100644 --- a/web/app.rb +++ b/web/app.rb @@ -646,6 +646,10 @@ ensure db&.close end +# Retrieve recent neighbour signal reports ordered by the recorded time. +# +# @param limit [Integer] maximum number of rows returned. +# @return [Array] neighbour tuples formatted for the API response. def query_neighbors(limit) db = open_database(readonly: true) db.results_as_hash = true @@ -666,6 +670,10 @@ ensure db&.close end +# Retrieve telemetry packets enriched with parsed numeric values. +# +# @param limit [Integer] maximum number of rows returned. +# @return [Array] telemetry rows suitable for serialisation. def query_telemetry(limit) db = open_database(readonly: true) db.results_as_hash = true @@ -1368,6 +1376,11 @@ def insert_position(db, payload) ) end +# Ingest neighbour relationship data for a node and refresh cached records. +# +# @param db [SQLite3::Database] open database handle. +# @param payload [Hash] neighbour payload provided by the data daemon. +# @return [void] def insert_neighbors(db, payload) return unless payload.is_a?(Hash) @@ -1461,6 +1474,14 @@ def insert_neighbors(db, payload) end end +# Update cached node metrics using the provided telemetry readings. +# +# @param db [SQLite3::Database] open database handle. +# @param node_id [String, nil] canonical node identifier when available. +# @param node_num [Integer, nil] numeric node reference. +# @param rx_time [Integer, nil] last receive timestamp associated with the telemetry. +# @param metrics [Hash] telemetry metrics extracted from the payload. +# @return [void] def update_node_from_telemetry(db, node_id, node_num, rx_time, metrics = {}) num = coerce_integer(node_num) id = string_or_nil(node_id) @@ -1512,6 +1533,11 @@ def update_node_from_telemetry(db, node_id, node_num, rx_time, metrics = {}) end end +# Insert or update a telemetry packet and propagate relevant metrics to the node. +# +# @param db [SQLite3::Database] open database handle. +# @param payload [Hash] telemetry payload provided by the data daemon. +# @return [void] def insert_telemetry(db, payload) return unless payload.is_a?(Hash) diff --git a/web/spec/app_spec.rb b/web/spec/app_spec.rb index 8fb4bee..178783e 100644 --- a/web/spec/app_spec.rb +++ b/web/spec/app_spec.rb @@ -23,10 +23,19 @@ require "base64" RSpec.describe "Potato Mesh Sinatra app" do let(:app) { Sinatra::Application } + # Return the absolute filesystem path to the requested fixture. + # + # @param name [String] fixture filename relative to the tests directory. + # @return [String] absolute path to the fixture file. def fixture_path(name) File.expand_path("../../tests/#{name}", __dir__) end + # Execute the provided block with a configured SQLite connection. + # + # @param readonly [Boolean] whether to open the database in read-only mode. + # @yieldparam db [SQLite3::Database] open database handle. + # @return [void] def with_db(readonly: false) db = SQLite3::Database.new(DB_PATH, readonly: readonly) db.busy_timeout = DB_BUSY_TIMEOUT_MS @@ -36,6 +45,9 @@ RSpec.describe "Potato Mesh Sinatra app" do db&.close end + # Remove all rows from the tables used by the application under test. + # + # @return [void] def clear_database with_db do |db| db.execute("DELETE FROM neighbors") @@ -46,10 +58,18 @@ RSpec.describe "Potato Mesh Sinatra app" do end end + # Build a hash excluding entries whose values are nil. + # + # @param hash [Hash] collection filtered for nil values. + # @return [Hash] hash containing only keys with non-nil values. def reject_nil_values(hash) hash.reject { |_, value| value.nil? } end + # Construct a request payload mirroring the structure produced by the daemon. + # + # @param node [Hash] node attributes from the fixture dataset. + # @return [Hash] payload formatted for the API. def build_node_payload(node) payload = { "user" => reject_nil_values( @@ -85,10 +105,18 @@ RSpec.describe "Potato Mesh Sinatra app" do payload end + # Determine the expected last heard timestamp for a node fixture. + # + # @param node [Hash] node attributes from the fixture dataset. + # @return [Integer, nil] canonical last heard timestamp. def expected_last_heard(node) [node["last_heard"], node["position_time"]].compact.max end + # Assemble the expected row persisted in the nodes table. + # + # @param node [Hash] node attributes from the fixture dataset. + # @return [Hash] expected database row for assertions. def expected_node_row(node) final_last = expected_last_heard(node) { @@ -114,6 +142,12 @@ RSpec.describe "Potato Mesh Sinatra app" do } end + # Assert equality while supporting tolerance for floating point comparisons. + # + # @param actual [Object] observed value. + # @param expected [Object] expected value. + # @param tolerance [Float] acceptable delta for floating point values. + # @return [void] def expect_same_value(actual, expected, tolerance: 1e-6) if expected.nil? expect(actual).to be_nil @@ -124,6 +158,9 @@ RSpec.describe "Potato Mesh Sinatra app" do end end + # Import all nodes defined in the fixture file via the HTTP API. + # + # @return [void] def import_nodes_fixture nodes_fixture.each do |node| payload = { node["node_id"] => build_node_payload(node) } @@ -133,6 +170,9 @@ RSpec.describe "Potato Mesh Sinatra app" do end end + # Import all messages defined in the fixture file via the HTTP API. + # + # @return [void] def import_messages_fixture messages_fixture.each do |message| payload = message.reject { |key, _| key == "node" }