[birds] Add much better bird importing
This commit is contained in:
130
tests/birds_tests/test_importer.py
Normal file
130
tests/birds_tests/test_importer.py
Normal file
@ -0,0 +1,130 @@
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user