125 lines
4.2 KiB
Python
125 lines
4.2 KiB
Python
|
|
"""Tests for output formatters (SQL, 3LE, JSON)."""
|
||
|
|
|
||
|
|
import json
|
||
|
|
|
||
|
|
from pg_orrery_catalog.output.json_out import generate_json
|
||
|
|
from pg_orrery_catalog.output.sql import escape_sql_string, generate_sql
|
||
|
|
from pg_orrery_catalog.output.tle_file import generate_3le
|
||
|
|
from pg_orrery_catalog.tle import TLERecord
|
||
|
|
|
||
|
|
|
||
|
|
def _make_record(norad_id: int, name: str = "", epoch: float = 24001.0) -> TLERecord:
|
||
|
|
line1 = f"1 {norad_id:05d}U 98067A {epoch:014.8f} .00000000 00000-0 00000-0 0 9990"
|
||
|
|
line2 = f"2 {norad_id:05d} 51.6400 100.0000 0007417 30.0000 330.1234 15.49456789999990"
|
||
|
|
return TLERecord(
|
||
|
|
line1=line1, line2=line2, name=name or f"SAT-{norad_id}",
|
||
|
|
norad_id=norad_id, epoch=epoch,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class TestSQLEscaping:
|
||
|
|
def test_single_quote(self):
|
||
|
|
assert escape_sql_string("it's") == "it''s"
|
||
|
|
|
||
|
|
def test_backslash(self):
|
||
|
|
assert escape_sql_string("a\\b") == "a\\\\b"
|
||
|
|
|
||
|
|
def test_both(self):
|
||
|
|
assert escape_sql_string("it's a\\b") == "it''s a\\\\b"
|
||
|
|
|
||
|
|
def test_clean(self):
|
||
|
|
assert escape_sql_string("HELLO") == "HELLO"
|
||
|
|
|
||
|
|
|
||
|
|
class TestSQLGeneration:
|
||
|
|
def test_basic_output(self):
|
||
|
|
records = {25544: _make_record(25544, "ISS (ZARYA)")}
|
||
|
|
sql = generate_sql(records, table="test_table")
|
||
|
|
assert "DROP TABLE IF EXISTS test_table;" in sql
|
||
|
|
assert "CREATE TABLE IF NOT EXISTS test_table" in sql
|
||
|
|
assert "INSERT INTO test_table" in sql
|
||
|
|
assert "ISS (ZARYA)" in sql
|
||
|
|
assert "E'" in sql # escape string syntax
|
||
|
|
assert "\\n" in sql # newline between TLE lines
|
||
|
|
|
||
|
|
def test_no_drop(self):
|
||
|
|
records = {1: _make_record(1)}
|
||
|
|
sql = generate_sql(records, table="t", drop_existing=False)
|
||
|
|
assert "DROP TABLE" not in sql
|
||
|
|
|
||
|
|
def test_sorted_by_norad(self):
|
||
|
|
records = {
|
||
|
|
99999: _make_record(99999),
|
||
|
|
1: _make_record(1),
|
||
|
|
25544: _make_record(25544),
|
||
|
|
}
|
||
|
|
sql = generate_sql(records)
|
||
|
|
lines = [line for line in sql.split("\n") if line.startswith("INSERT")]
|
||
|
|
assert len(lines) == 3
|
||
|
|
# First INSERT should be NORAD 1 (lowest)
|
||
|
|
assert "SAT-1" in lines[0]
|
||
|
|
|
||
|
|
def test_name_escaping(self):
|
||
|
|
records = {1: _make_record(1, "O'BRIEN SAT")}
|
||
|
|
sql = generate_sql(records)
|
||
|
|
assert "O''BRIEN SAT" in sql
|
||
|
|
|
||
|
|
def test_default_name(self):
|
||
|
|
"""When TLERecord has no name, SQL output uses 'NORAD {id}'."""
|
||
|
|
rec = _make_record(1)
|
||
|
|
rec.name = "" # clear the name to test fallback
|
||
|
|
records = {1: rec}
|
||
|
|
sql = generate_sql(records)
|
||
|
|
assert "NORAD 1" in sql
|
||
|
|
|
||
|
|
|
||
|
|
class TestTLEFileGeneration:
|
||
|
|
def test_3le_format(self):
|
||
|
|
records = {25544: _make_record(25544, "ISS (ZARYA)")}
|
||
|
|
text = generate_3le(records)
|
||
|
|
lines = text.strip().split("\n")
|
||
|
|
assert len(lines) == 3
|
||
|
|
assert lines[0].startswith("0 ")
|
||
|
|
assert lines[1].startswith("1 ")
|
||
|
|
assert lines[2].startswith("2 ")
|
||
|
|
|
||
|
|
def test_sorted_output(self):
|
||
|
|
records = {
|
||
|
|
2: _make_record(2, "B"),
|
||
|
|
1: _make_record(1, "A"),
|
||
|
|
}
|
||
|
|
text = generate_3le(records)
|
||
|
|
lines = text.strip().split("\n")
|
||
|
|
assert "A" in lines[0]
|
||
|
|
|
||
|
|
def test_name_prefix(self):
|
||
|
|
records = {1: _make_record(1, "0 ALREADY PREFIXED")}
|
||
|
|
text = generate_3le(records)
|
||
|
|
# Should not double-prefix
|
||
|
|
assert "0 0 ALREADY PREFIXED" not in text
|
||
|
|
assert "0 ALREADY PREFIXED" in text
|
||
|
|
|
||
|
|
|
||
|
|
class TestJSONGeneration:
|
||
|
|
def test_valid_json(self):
|
||
|
|
records = {25544: _make_record(25544, "ISS")}
|
||
|
|
text = generate_json(records)
|
||
|
|
data = json.loads(text)
|
||
|
|
assert isinstance(data, list)
|
||
|
|
assert len(data) == 1
|
||
|
|
assert data[0]["norad_id"] == 25544
|
||
|
|
assert data[0]["name"] == "ISS"
|
||
|
|
|
||
|
|
def test_fields_present(self):
|
||
|
|
records = {1: _make_record(1)}
|
||
|
|
data = json.loads(generate_json(records))
|
||
|
|
entry = data[0]
|
||
|
|
for field in ("norad_id", "name", "line1", "line2", "epoch", "mean_motion",
|
||
|
|
"inclination", "eccentricity", "regime", "source"):
|
||
|
|
assert field in entry
|
||
|
|
|
||
|
|
def test_regime_classification(self):
|
||
|
|
records = {1: _make_record(1)} # mean_motion ~15.49 → LEO
|
||
|
|
data = json.loads(generate_json(records))
|
||
|
|
assert data[0]["regime"] == "LEO"
|