"""Tests for `import_checkpoints --replace` — refresh existing checkpoints in place (e.g. swapping hand-authored YouTube segments for real chapter markers), without ever blanking a resource.""" from io import StringIO import pytest from django.core.management import call_command from apps.resources import importers from apps.resources.models import Resource, ResourceCheckpoint pytestmark = pytest.mark.django_db def _video(n_existing): r = Resource.objects.create( title='Learn X', provider='YouTube', url='https://www.youtube.com/watch?v=abc123', difficulty_level='BEGINNER', duration=300, type='VIDEO', ) for i in range(1, n_existing + 1): ResourceCheckpoint.objects.create( resource=r, order_index=i, title=f'manual {i}', source='manual', ) return r def _stub_extract(monkeypatch, titles, source='youtube_api'): def fake(url, **kwargs): return importers.ExtractResult( checkpoints=[ importers.ExtractedCheckpoint(order_index=i, title=t) for i, t in enumerate(titles, start=1) ], source=source, provider='youtube', ) # patch where the command looked it up from apps.resources.management.commands import import_checkpoints as cmd monkeypatch.setattr(cmd, 'extract_checkpoints', fake) def _run(*args): out, err = StringIO(), StringIO() call_command('import_checkpoints', *args, stdout=out, stderr=err) return out.getvalue() + err.getvalue() def test_replace_swaps_when_extraction_succeeds(monkeypatch): r = _video(5) _stub_extract(monkeypatch, ['Intro', 'Setup', 'Deep Dive']) _run('--type', 'VIDEO', '--replace', '--apply') rows = list(r.checkpoints.order_by('order_index')) assert [c.title for c in rows] == ['Intro', 'Setup', 'Deep Dive'] assert all(c.source == 'youtube_api' for c in rows) # the 5 old manual rows are gone (replaced, not appended) assert r.checkpoints.count() == 3 def test_replace_keeps_existing_when_extraction_under_two(monkeypatch): r = _video(5) _stub_extract(monkeypatch, ['only one']) # <2 → must not blank _run('--type', 'VIDEO', '--replace', '--apply') rows = list(r.checkpoints.order_by('order_index')) assert [c.title for c in rows] == ['manual 1', 'manual 2', 'manual 3', 'manual 4', 'manual 5'] assert all(c.source == 'manual' for c in rows) def test_replace_requires_apply(monkeypatch): r = _video(5) _stub_extract(monkeypatch, ['Intro', 'Setup']) out = _run('--type', 'VIDEO', '--replace') # no --apply assert 'requires --apply' in out # untouched assert r.checkpoints.count() == 5 assert r.checkpoints.first().source == 'manual' def test_default_run_ignores_resources_that_have_checkpoints(monkeypatch): """Without --replace, a resource that already has checkpoints is skipped (the n=0 backfill gate), so existing rows are never touched.""" r = _video(5) _stub_extract(monkeypatch, ['Intro', 'Setup', 'Deep Dive']) _run('--type', 'VIDEO', '--apply') # no --replace assert r.checkpoints.count() == 5 assert r.checkpoints.first().source == 'manual'