"""Unit tests for port traffic compare: interface + optional mapped baseline.""" from __future__ import annotations import unittest from datetime import datetime, timedelta from uuid import uuid4 from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from sqlalchemy.pool import StaticPool from netx_api.db import Base from netx_api.models import PortTrafficDevice, PortTrafficSample, PortTrafficSeries, PortTrafficTarget from netx_api.port_traffic_service import baseline_offset_hours, compare_targets class BaselineOffsetTests(unittest.TestCase): def test_presets(self): self.assertIsNone(baseline_offset_hours("off", 24, None)) self.assertEqual(baseline_offset_hours("shift", 24, None), 24.0) self.assertEqual(baseline_offset_hours("day", 6, None), 24.0) self.assertEqual(baseline_offset_hours("week", 24, None), 168.0) self.assertEqual(baseline_offset_hours("custom", 24, 36), 36.0) class CompareTargetsTests(unittest.TestCase): def setUp(self): engine = create_engine( "sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool, ) Base.metadata.create_all(bind=engine) self.Session = sessionmaker(bind=engine) self.db = self.Session() self.device_id = uuid4().hex self.device2_id = uuid4().hex self.series_id = uuid4().hex self.target_id = uuid4().hex self.mapped_id = uuid4().hex now = datetime(2026, 7, 31, 12, 0, 0) self.now = now self.db.add( PortTrafficDevice( id=self.device_id, source="managed", ne_id="ne1", ne_name="R1", vendor="Huawei", status="running", created_at=now, updated_at=now, ) ) self.db.add( PortTrafficDevice( id=self.device2_id, source="managed", ne_id="ne2", ne_name="R2", vendor="ZTE", status="running", created_at=now, updated_at=now, ) ) self.db.add( PortTrafficSeries( id=self.series_id, device_id=self.device_id, title="uplink1", status="active", created_at=now, ) ) self.db.add( PortTrafficTarget( id=self.target_id, device_id=self.device_id, series_id=self.series_id, source="managed", target_id="ne1", ne_name="R1", vendor="Huawei", ifname="Ethernet1/0/0", status="active", created_at=now, ) ) self.db.add( PortTrafficTarget( id=self.mapped_id, device_id=self.device2_id, series_id="", source="managed", target_id="ne2", ne_name="R2", vendor="ZTE", ifname="gei-0/0/1", status="active", created_at=now, ) ) for i in range(3): self.db.add( PortTrafficSample( id=uuid4().hex, target_row_id=self.target_id, series_id=self.series_id, ts=now - timedelta(hours=1, minutes=30) + timedelta(minutes=30 * i), in_bps=1000 + i, out_bps=2000 + i, raw_ok=True, ) ) for i in range(3): self.db.add( PortTrafficSample( id=uuid4().hex, target_row_id=self.target_id, series_id=self.series_id, ts=now - timedelta(days=1, hours=1, minutes=30) + timedelta(minutes=30 * i), in_bps=500 + i, out_bps=800 + i, raw_ok=True, ) ) for i in range(2): self.db.add( PortTrafficSample( id=uuid4().hex, target_row_id=self.mapped_id, series_id="", ts=now - timedelta(hours=1) + timedelta(minutes=30 * i), in_bps=90 + i, out_bps=110 + i, raw_ok=True, ) ) self.db.commit() def tearDown(self): self.db.close() def test_off_returns_current_only(self): out = compare_targets( self.db, target_row_id=self.target_id, range_hours=2, baseline="off", to_ts=self.now, ) self.assertEqual(len(out.current), 3) self.assertEqual(len(out.baseline), 0) self.assertEqual(out.meta.target_id, self.target_id) def test_day_baseline_same_port(self): out = compare_targets( self.db, target_row_id=self.target_id, range_hours=2, baseline="day", to_ts=self.now, ) self.assertEqual(len(out.current), 3) self.assertEqual(len(out.baseline), 3) self.assertEqual(out.meta.offset_hours, 24.0) for p in out.baseline: self.assertIsNotNone(p.ts_raw) self.assertGreaterEqual(p.ts, self.now - timedelta(hours=2)) self.assertLessEqual(p.ts, self.now + timedelta(seconds=5)) self.assertEqual(out.baseline[0].in_bps, 500.0) def test_mapped_port_same_window(self): out = compare_targets( self.db, target_row_id=self.target_id, range_hours=2, baseline="off", baseline_target_id=self.mapped_id, to_ts=self.now, ) self.assertEqual(len(out.current), 3) self.assertEqual(len(out.baseline), 2) self.assertEqual(out.meta.baseline_target_id, self.mapped_id) self.assertEqual(out.baseline[0].in_bps, 90.0) self.assertIsNone(out.baseline[0].ts_raw) def test_mapped_port_with_day_offset(self): for i in range(2): self.db.add( PortTrafficSample( id=uuid4().hex, target_row_id=self.mapped_id, ts=self.now - timedelta(days=1, hours=1) + timedelta(minutes=30 * i), in_bps=40 + i, out_bps=50 + i, raw_ok=True, ) ) self.db.commit() out = compare_targets( self.db, target_row_id=self.target_id, range_hours=2, baseline="day", baseline_target_id=self.mapped_id, to_ts=self.now, ) self.assertEqual(len(out.baseline), 2) self.assertEqual(out.baseline[0].in_bps, 40.0) self.assertIsNotNone(out.baseline[0].ts_raw) def test_shift_baseline(self): for i in range(2): self.db.add( PortTrafficSample( id=uuid4().hex, target_row_id=self.target_id, series_id=self.series_id, ts=self.now - timedelta(hours=3, minutes=30) + timedelta(minutes=30 * i), in_bps=50 + i, out_bps=60 + i, raw_ok=True, ) ) self.db.commit() out = compare_targets( self.db, target_row_id=self.target_id, range_hours=2, baseline="shift", to_ts=self.now, ) self.assertGreaterEqual(len(out.baseline), 2) self.assertEqual(out.meta.offset_hours, 2.0) def test_ahead_hours_extends_baseline_past_now(self): # Yesterday sample 30min after "now" clock — only visible when ahead_hours > 0. future_raw = self.now - timedelta(days=1) + timedelta(minutes=30) self.db.add( PortTrafficSample( id=uuid4().hex, target_row_id=self.target_id, series_id=self.series_id, ts=future_raw, in_bps=777.0, out_bps=888.0, raw_ok=True, ) ) self.db.commit() without = compare_targets( self.db, target_row_id=self.target_id, range_hours=2, baseline="day", ahead_hours=0, to_ts=self.now, ) self.assertFalse(any(abs((p.ts_raw or p.ts) - future_raw).total_seconds() < 1 for p in without.baseline)) with_ahead = compare_targets( self.db, target_row_id=self.target_id, range_hours=2, baseline="day", ahead_hours=1, to_ts=self.now, ) self.assertEqual(with_ahead.meta.ahead_hours, 1.0) hit = [p for p in with_ahead.baseline if p.in_bps == 777.0] self.assertEqual(len(hit), 1) self.assertGreater(hit[0].ts, self.now) self.assertLessEqual(hit[0].ts, self.now + timedelta(hours=1, seconds=5)) if __name__ == "__main__": unittest.main()