140 lines
5.1 KiB
Python
140 lines
5.1 KiB
Python
from datetime import timedelta
|
|
|
|
import pytest
|
|
from birds.importer import (
|
|
import_birding_csv,
|
|
parse_bool,
|
|
parse_coords,
|
|
parse_duration,
|
|
parse_int,
|
|
parse_timestamp,
|
|
)
|
|
from birds.models import Bird, BirdingLocation, BirdingCSVImport
|
|
from scrobbles.models import Scrobble
|
|
|
|
|
|
class TestParserHelpers:
|
|
def test_parse_duration(self):
|
|
assert parse_duration("9 minute(s)") == 9
|
|
assert parse_duration("120 minute(s)") == 120
|
|
assert parse_duration("") is None
|
|
assert parse_duration(None) is None
|
|
assert parse_duration("not a duration") is None
|
|
|
|
def test_parse_coords(self):
|
|
loc = "Some Place, US (44.384, -68.805)"
|
|
lat, lon = parse_coords(loc)
|
|
assert lat == 44.384
|
|
assert lon == -68.805
|
|
|
|
def test_parse_coords_no_match(self):
|
|
loc = "Some Place, US"
|
|
lat, lon = parse_coords(loc)
|
|
assert lat is None
|
|
assert lon is None
|
|
|
|
def test_parse_timestamp(self):
|
|
dt = parse_timestamp("May 10, 2026", "4:15 PM")
|
|
assert dt is not None
|
|
assert dt.year == 2026
|
|
assert dt.month == 5
|
|
assert dt.day == 10
|
|
assert dt.hour == 16
|
|
assert dt.minute == 15
|
|
|
|
def test_parse_timestamp_no_time(self):
|
|
dt = parse_timestamp("May 10, 2026", "")
|
|
assert dt is not None
|
|
assert dt.year == 2026
|
|
|
|
def test_parse_timestamp_invalid(self):
|
|
assert parse_timestamp("not a date", "") is None
|
|
|
|
def test_parse_bool(self):
|
|
assert parse_bool("true") is True
|
|
assert parse_bool("True") is True
|
|
assert parse_bool("yes") is True
|
|
assert parse_bool("1") is True
|
|
assert parse_bool("false") is False
|
|
assert parse_bool("") is None
|
|
assert parse_bool(None) is None
|
|
|
|
def test_parse_int(self):
|
|
assert parse_int("42") == 42
|
|
assert parse_int("") is None
|
|
assert parse_int(None) is None
|
|
assert parse_int("not a number") is None
|
|
|
|
|
|
class TestImportBirdingCSV:
|
|
def test_import_creates_birds(self, user, birding_csv_file):
|
|
import_birding_csv(birding_csv_file, user.id)
|
|
assert Bird.objects.filter(common_name="Canada Goose").exists()
|
|
assert Bird.objects.filter(common_name="Northern Cardinal").exists()
|
|
|
|
def test_import_creates_location(self, user, birding_csv_file):
|
|
import_birding_csv(birding_csv_file, user.id)
|
|
assert BirdingLocation.objects.filter(title="Test Park").exists()
|
|
|
|
def test_import_creates_scrobble(self, user, birding_csv_file):
|
|
import_birding_csv(birding_csv_file, user.id)
|
|
assert Scrobble.objects.filter(
|
|
source="Birding CSV Import"
|
|
).count() == 1
|
|
|
|
def test_import_logdata_fields(self, user, birding_csv_file):
|
|
import_birding_csv(birding_csv_file, user.id)
|
|
scrobble = Scrobble.objects.filter(source="Birding CSV Import").first()
|
|
log = scrobble.log
|
|
assert log["duration_minutes"] == 9
|
|
assert log["observation_type"] == "Stationary"
|
|
assert log["party_size"] == 4
|
|
assert log["complete_checklist"] is True
|
|
assert len(log["birds"]) == 2
|
|
|
|
def test_import_sighting_details(self, user, birding_csv_file):
|
|
import_birding_csv(birding_csv_file, user.id)
|
|
scrobble = Scrobble.objects.filter(source="Birding CSV Import").first()
|
|
birds = scrobble.log["birds"]
|
|
cardinal = next(b for b in birds if b["quantity"] == 2)
|
|
assert cardinal["sighting_notes"] == "At the feeder"
|
|
|
|
def test_import_idempotent(self, user, birding_csv_file):
|
|
import_birding_csv(birding_csv_file, user.id)
|
|
import_birding_csv(birding_csv_file, user.id)
|
|
assert Scrobble.objects.filter(
|
|
source="Birding CSV Import"
|
|
).count() == 1
|
|
|
|
def test_import_bird_quantities(self, user, birding_csv_file):
|
|
import_birding_csv(birding_csv_file, user.id)
|
|
scrobble = Scrobble.objects.filter(source="Birding CSV Import").first()
|
|
birds = scrobble.log["birds"]
|
|
goose = next(b for b in birds if b["quantity"] == 6)
|
|
assert goose is not None
|
|
|
|
def test_import_sets_stop_timestamp(self, user, birding_csv_file):
|
|
import_birding_csv(birding_csv_file, user.id)
|
|
scrobble = Scrobble.objects.filter(source="Birding CSV Import").first()
|
|
assert scrobble.stop_timestamp is not None
|
|
expected = scrobble.timestamp + timedelta(minutes=9)
|
|
assert scrobble.stop_timestamp == expected
|
|
|
|
|
|
class TestBirdingCSVImportModel:
|
|
def test_create_import_model(self, db, user):
|
|
imp = BirdingCSVImport.objects.create(user=user)
|
|
assert imp.uuid is not None
|
|
assert imp.import_type == "Birding CSV"
|
|
assert "Birding" in str(imp)
|
|
|
|
@pytest.mark.django_db(transaction=True)
|
|
def test_process_via_model(self, user, birding_csv_file):
|
|
imp = BirdingCSVImport.objects.create(user=user)
|
|
with open(birding_csv_file, "rb") as f:
|
|
imp.csv_file.save("test.csv", f, save=True)
|
|
imp.process()
|
|
imp.refresh_from_db()
|
|
assert imp.process_count == 1
|
|
assert imp.processed_finished is not None
|