fix(v2-import): namespace source identity for snapshot upserts
This commit is contained in:
@ -51,6 +51,14 @@ def _valid_payload() -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _valid_payload_for_source(source_name: str, *, competition_name: str = "NBA", team_name: str = "Los Angeles Lakers") -> dict:
|
||||
payload = _valid_payload()
|
||||
payload["source_name"] = source_name
|
||||
payload["records"][0]["competition_name"] = competition_name
|
||||
payload["records"][0]["team_name"] = team_name
|
||||
return payload
|
||||
|
||||
|
||||
def _write_json(path: Path, payload: dict) -> None:
|
||||
path.write_text(json.dumps(payload), encoding="utf-8")
|
||||
|
||||
@ -87,9 +95,9 @@ def test_valid_snapshot_import(tmp_path, settings):
|
||||
assert (archive / "nba-2026-03-13.json").exists()
|
||||
assert not (incoming / "nba-2026-03-13.json").exists()
|
||||
|
||||
assert Competition.objects.filter(source_uid="comp-nba").exists()
|
||||
assert Team.objects.filter(source_uid="team-lal").exists()
|
||||
assert Player.objects.filter(source_uid="player-23").exists()
|
||||
assert Competition.objects.filter(source_name="official_site_feed", source_uid="comp-nba").exists()
|
||||
assert Team.objects.filter(source_name="official_site_feed", source_uid="team-lal").exists()
|
||||
assert Player.objects.filter(source_name="official_site_feed", source_uid="player-23").exists()
|
||||
assert Season.objects.filter(source_uid="season:2025-2026").exists()
|
||||
assert PlayerSeason.objects.count() == 1
|
||||
assert PlayerSeasonStats.objects.count() == 1
|
||||
@ -187,3 +195,59 @@ def test_same_run_second_file_same_checksum_is_skipped(tmp_path, settings):
|
||||
assert files["a.json"].status == ImportFile.FileStatus.SUCCESS
|
||||
assert files["b.json"].status == ImportFile.FileStatus.SKIPPED
|
||||
assert files["a.json"].checksum == files["b.json"].checksum
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_same_raw_external_ids_from_different_sources_do_not_collide(tmp_path, settings):
|
||||
incoming = tmp_path / "incoming"
|
||||
archive = tmp_path / "archive"
|
||||
failed = tmp_path / "failed"
|
||||
incoming.mkdir()
|
||||
archive.mkdir()
|
||||
failed.mkdir()
|
||||
|
||||
lba_payload = _valid_payload_for_source("lba", competition_name="Lega Basket Serie A", team_name="Virtus Bologna")
|
||||
bcl_payload = _valid_payload_for_source("bcl", competition_name="Basketball Champions League", team_name="AEK Athens")
|
||||
|
||||
_write_json(incoming / "lba.json", lba_payload)
|
||||
_write_json(incoming / "bcl.json", bcl_payload)
|
||||
|
||||
settings.STATIC_DATASET_INCOMING_DIR = str(incoming)
|
||||
settings.STATIC_DATASET_ARCHIVE_DIR = str(archive)
|
||||
settings.STATIC_DATASET_FAILED_DIR = str(failed)
|
||||
|
||||
call_command("import_snapshots")
|
||||
|
||||
assert Competition.objects.filter(source_uid="comp-nba").count() == 2
|
||||
assert Team.objects.filter(source_uid="team-lal").count() == 2
|
||||
assert Player.objects.filter(source_uid="player-23").count() == 2
|
||||
assert Competition.objects.filter(source_name="lba", source_uid="comp-nba", name="Lega Basket Serie A").exists()
|
||||
assert Competition.objects.filter(source_name="bcl", source_uid="comp-nba", name="Basketball Champions League").exists()
|
||||
assert Team.objects.filter(source_name="lba", source_uid="team-lal", name="Virtus Bologna").exists()
|
||||
assert Team.objects.filter(source_name="bcl", source_uid="team-lal", name="AEK Athens").exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_reimport_same_source_payload_remains_idempotent(tmp_path, settings):
|
||||
incoming = tmp_path / "incoming"
|
||||
archive = tmp_path / "archive"
|
||||
failed = tmp_path / "failed"
|
||||
incoming.mkdir()
|
||||
archive.mkdir()
|
||||
failed.mkdir()
|
||||
|
||||
payload = _valid_payload_for_source("lba")
|
||||
_write_json(incoming / "lba-1.json", payload)
|
||||
|
||||
settings.STATIC_DATASET_INCOMING_DIR = str(incoming)
|
||||
settings.STATIC_DATASET_ARCHIVE_DIR = str(archive)
|
||||
settings.STATIC_DATASET_FAILED_DIR = str(failed)
|
||||
|
||||
call_command("import_snapshots")
|
||||
|
||||
_write_json(incoming / "lba-2.json", payload)
|
||||
call_command("import_snapshots")
|
||||
|
||||
assert Competition.objects.filter(source_name="lba", source_uid="comp-nba").count() == 1
|
||||
assert Team.objects.filter(source_name="lba", source_uid="team-lal").count() == 1
|
||||
assert Player.objects.filter(source_name="lba", source_uid="player-23").count() == 1
|
||||
|
||||
@ -8,59 +8,31 @@ from apps.competitions.models import Competition, Season
|
||||
from apps.ingestion.models import ImportFile, ImportRun
|
||||
from apps.players.models import Nationality, Player, Position, Role
|
||||
from apps.scouting.models import FavoritePlayer, SavedSearch
|
||||
from apps.stats.models import PlayerSeason
|
||||
from apps.teams.models import Team
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_player_unique_full_name_birth_date_constraint():
|
||||
nationality = Nationality.objects.create(name="Italy", iso2_code="IT", iso3_code="ITA")
|
||||
position = Position.objects.create(code="PG", name="Point Guard")
|
||||
role = Role.objects.create(code="playmaker", name="Playmaker")
|
||||
|
||||
Player.objects.create(
|
||||
first_name="Marco",
|
||||
last_name="Rossi",
|
||||
full_name="Marco Rossi",
|
||||
birth_date=date(2001, 1, 1),
|
||||
nationality=nationality,
|
||||
nominal_position=position,
|
||||
inferred_role=role,
|
||||
source_uid="player-src-1",
|
||||
)
|
||||
|
||||
with pytest.raises(IntegrityError):
|
||||
Player.objects.create(
|
||||
first_name="Marco",
|
||||
last_name="Rossi",
|
||||
full_name="Marco Rossi",
|
||||
birth_date=date(2001, 1, 1),
|
||||
nationality=nationality,
|
||||
nominal_position=position,
|
||||
inferred_role=role,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_source_uid_uniqueness_on_core_entities():
|
||||
season = Season.objects.create(
|
||||
def test_source_uid_uniqueness_is_scoped_by_source_name():
|
||||
Season.objects.create(
|
||||
source_uid="season-2024",
|
||||
label="2024-2025",
|
||||
start_date=date(2024, 10, 1),
|
||||
end_date=date(2025, 6, 30),
|
||||
)
|
||||
competition = Competition.objects.create(
|
||||
Competition.objects.create(
|
||||
source_name="lba",
|
||||
source_uid="comp-001",
|
||||
name="Serie A",
|
||||
slug="serie-a",
|
||||
competition_type=Competition.CompetitionType.LEAGUE,
|
||||
)
|
||||
team = Team.objects.create(source_uid="team-001", name="Virtus Bologna", slug="virtus-bologna")
|
||||
Team.objects.create(source_name="lba", source_uid="team-001", name="Virtus Bologna", slug="virtus-bologna")
|
||||
|
||||
nationality = Nationality.objects.create(name="Spain", iso2_code="ES", iso3_code="ESP")
|
||||
position = Position.objects.create(code="SF", name="Small Forward")
|
||||
role = Role.objects.create(code="wing", name="Wing")
|
||||
player = Player.objects.create(
|
||||
Player.objects.create(
|
||||
source_name="lba",
|
||||
source_uid="player-001",
|
||||
first_name="Juan",
|
||||
last_name="Perez",
|
||||
@ -71,16 +43,32 @@ def test_source_uid_uniqueness_on_core_entities():
|
||||
inferred_role=role,
|
||||
)
|
||||
|
||||
PlayerSeason.objects.create(
|
||||
source_uid="ps-001",
|
||||
player=player,
|
||||
season=season,
|
||||
team=team,
|
||||
competition=competition,
|
||||
Competition.objects.create(
|
||||
source_name="bcl",
|
||||
source_uid="comp-001",
|
||||
name="BCL",
|
||||
slug="bcl",
|
||||
competition_type=Competition.CompetitionType.INTERNATIONAL,
|
||||
)
|
||||
Team.objects.create(source_name="bcl", source_uid="team-001", name="AEK", slug="aek")
|
||||
Player.objects.create(
|
||||
source_name="bcl",
|
||||
source_uid="player-001",
|
||||
first_name="Juan",
|
||||
last_name="Perez",
|
||||
full_name="Juan Perez",
|
||||
birth_date=date(2000, 5, 1),
|
||||
nationality=nationality,
|
||||
nominal_position=position,
|
||||
inferred_role=role,
|
||||
)
|
||||
|
||||
assert Competition.objects.filter(source_uid="comp-001").count() == 2
|
||||
assert Team.objects.filter(source_uid="team-001").count() == 2
|
||||
assert Player.objects.filter(source_uid="player-001").count() == 2
|
||||
|
||||
with pytest.raises(IntegrityError):
|
||||
Team.objects.create(source_uid="team-001", name="Another Team", slug="another-team")
|
||||
Team.objects.create(source_name="lba", source_uid="team-001", name="Another Team", slug="another-team")
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
||||
Reference in New Issue
Block a user