sessions / ed2ed59f655e6994a03cc453085d207c41ab3cf9

/home/powellc/src/code.unbl.ink/secstate/vrobbler

This commit has no recorded session.

diff

show whitespace

commit ed2ed59f655e6994a03cc453085d207c41ab3cf9Author: Colin Powell <colin@unbl.ink>Date:   Tue Jun 9 12:48:01 2026 -0400    [scrobbles] Fix tests around visbilitydiff --git a/PROJECT.org b/PROJECT.orgindex 6eab67d..98eb370 100644--- a/PROJECT.org+++ b/PROJECT.org@@ -88,7 +88,7 @@ fetching and simple saving. *** Metadata sources **** Scraper -* Backlog [0/14] :vrobbler:project:personal:+* Backlog [1/16] :vrobbler:project:personal: ** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :vrobbler:personal:bug:music:scrobbles: :PROPERTIES: :ID:       702462cf-d54b-48c6-8a7c-78b8de751deb@@ -534,7 +534,27 @@ async with the POST data stored in the log["raw_data"] and used by the celery en to go try to enrich the media instance.  Should this enrichment fail, tag the scrobble as "enrichment-failed" log a warning and move on. +** TODO [#A] Allow updating all a user's scrobble visibility at once :scrobbles:sharing:feature:+:PROPERTIES:+:ID:       9ed2ec65-bf69-4300-965c-6a7d3ef7ea03+:END:++*** Description++We now have the ability to share or unshare scrobbles and create private links. We should add a toggle+in the user's settings that will bulk make all their scrobbles public or private, so that a user+can either share everything, or lock their account down. +This should not affect scrobbles that are  in the "Shared" visibility state.++Additionally, users's should have links in their settings to see what scrobbles+are either public, shared or private. Probably this could be done with a+?visbility=<> filter on the /scrobbles/ page.+         +** DONE [#A] Fix broken tests with new sharing and add tests :scrobbles:sharing:tests:+:PROPERTIES:+:ID:       10ecd169-eaee-8554-d4ee-f1d34bfad99f+:END: * Version 48.3 [1/1] ** DONE [#A] Fix bug in missing sqids dep :dependencies:project: :PROPERTIES:diff --git a/tests/scrobbles_tests/test_views.py b/tests/scrobbles_tests/test_views.pyindex c8f1e6e..be26a75 100644--- a/tests/scrobbles_tests/test_views.py+++ b/tests/scrobbles_tests/test_views.py@@ -8,7 +8,8 @@ from django.urls import reverse from django.utils import timezone from music.models import Album, Artist, Track from podcasts.models import PodcastEpisode-from scrobbles.models import Scrobble+from scrobbles.models import Scrobble, ShareViewLog+from scrobbles.sqids import encode_scrobble_share from tasks.models import Task  @@ -512,6 +513,7 @@ def test_scrobble_detail_view_with_notes_as_flat_list(client):         task=task,         media_type="Task",         user=user,+        visibility="public",         log={             "notes": ["First note", "Second note"],             "description": "Test description",@@ -534,6 +536,7 @@ def test_scrobble_detail_view_with_notes_as_dict_timestamps(client):         task=task,         media_type="Task",         user=user,+        visibility="public",         log={             "notes": [                 {"2024-01-01 10:00:00": "Note at first timestamp"},@@ -562,6 +565,7 @@ def test_scrobble_detail_view_with_notes_and_labels(client):         task=task,         media_type="Task",         user=user,+        visibility="public",         log={             "notes": [                 {"2024-01-01 10:00:00": "Note with label"},@@ -739,3 +743,293 @@ def test_gps_webhook_creates_location(client, valid_auth_token):     )     assert response.status_code == 200     assert "scrobble_id" in response.data+++@pytest.mark.django_db+def test_share_view_shared_visibility(client):+    user = get_user_model().objects.create_user(+        username="shareuser", password="testpass"+    )+    task = Task.objects.create(title="Test Task")+    scrobble = Scrobble.objects.create(+        task=task, media_type="Task", user=user, visibility="shared",+        timestamp=timezone.now(),+    )+    url = reverse(+        "scrobbles:shared-detail",+        kwargs={"sqid": encode_scrobble_share(scrobble.id, scrobble.share_token_version)},+    )+    response = client.get(url)+    assert response.status_code == 200+++@pytest.mark.django_db+def test_share_view_public_visibility(client):+    user = get_user_model().objects.create_user(+        username="shareuser2", password="testpass"+    )+    task = Task.objects.create(title="Test Task")+    scrobble = Scrobble.objects.create(+        task=task, media_type="Task", user=user, visibility="public",+        timestamp=timezone.now(),+    )+    url = reverse(+        "scrobbles:shared-detail",+        kwargs={"sqid": encode_scrobble_share(scrobble.id, scrobble.share_token_version)},+    )+    response = client.get(url)+    assert response.status_code == 200+++@pytest.mark.django_db+def test_share_view_private_visibility_returns_404(client):+    user = get_user_model().objects.create_user(+        username="shareuser3", password="testpass"+    )+    task = Task.objects.create(title="Test Task")+    scrobble = Scrobble.objects.create(+        task=task, media_type="Task", user=user, visibility="private",+        timestamp=timezone.now(),+    )+    url = reverse(+        "scrobbles:shared-detail",+        kwargs={"sqid": encode_scrobble_share(scrobble.id, scrobble.share_token_version)},+    )+    response = client.get(url)+    assert response.status_code == 404+++@pytest.mark.django_db+def test_share_view_invalid_sqid_returns_404(client):+    url = reverse("scrobbles:shared-detail", kwargs={"sqid": "InvalidSqid123"})+    response = client.get(url)+    assert response.status_code == 404+++@pytest.mark.django_db+def test_share_view_expired_token_returns_404(client):+    user = get_user_model().objects.create_user(+        username="shareuser4", password="testpass"+    )+    task = Task.objects.create(title="Test Task")+    scrobble = Scrobble.objects.create(+        task=task, media_type="Task", user=user, visibility="shared",+        timestamp=timezone.now(),+    )+    old_sqid = encode_scrobble_share(scrobble.id, scrobble.share_token_version)+    scrobble.regenerate_share_token()+    url = reverse("scrobbles:shared-detail", kwargs={"sqid": old_sqid})+    response = client.get(url)+    assert response.status_code == 404+++@pytest.mark.django_db+def test_share_view_increments_count_and_logs_view(client):+    user = get_user_model().objects.create_user(+        username="shareuser5", password="testpass"+    )+    task = Task.objects.create(title="Test Task")+    scrobble = Scrobble.objects.create(+        task=task, media_type="Task", user=user, visibility="shared",+        timestamp=timezone.now(),+    )+    assert scrobble.share_view_count == 0++    url = reverse(+        "scrobbles:shared-detail",+        kwargs={"sqid": encode_scrobble_share(scrobble.id, scrobble.share_token_version)},+    )+    response = client.get(url)+    assert response.status_code == 200++    scrobble.refresh_from_db()+    assert scrobble.share_view_count == 1+    assert ShareViewLog.objects.filter(scrobble=scrobble).count() == 1++    log_entry = ShareViewLog.objects.filter(scrobble=scrobble).first()+    assert log_entry.ip_address == "127.0.0.1"+    assert log_entry.user_agent == ""+    assert log_entry.referrer == ""+++@pytest.mark.django_db+def test_explore_view_shows_only_public_scrobbles(client):+    user = get_user_model().objects.create_user(+        username="exploreuser", password="testpass"+    )+    public_task = Task.objects.create(title="Public Task Title")+    shared_task = Task.objects.create(title="Shared Task Title")+    private_task = Task.objects.create(title="Private Task Title")+    ts = timezone.now()+    public_scrobble = Scrobble.objects.create(+        task=public_task, media_type="Task", user=user, visibility="public",+        timestamp=ts,+    )+    Scrobble.objects.create(+        task=shared_task, media_type="Task", user=user, visibility="shared",+        timestamp=ts,+    )+    Scrobble.objects.create(+        task=private_task, media_type="Task", user=user, visibility="private",+        timestamp=ts,+    )++    url = reverse("scrobbles:explore")+    response = client.get(url)+    assert response.status_code == 200++    content = response.content.decode()+    assert "Public Task Title" in content+    assert "Shared Task Title" not in content+    assert "Private Task Title" not in content+++@pytest.mark.django_db+def test_change_visibility_owner_can_change(client):+    user = get_user_model().objects.create_user(+        username="visuser", password="testpass"+    )+    task = Task.objects.create(title="Test Task")+    scrobble = Scrobble.objects.create(+        task=task, media_type="Task", user=user, visibility="private",+        timestamp=timezone.now(),+    )++    client.force_login(user)+    url = reverse("scrobbles:change-visibility", kwargs={"uuid": scrobble.uuid})+    response = client.post(url, {"visibility": "shared"})+    assert response.status_code == 302++    scrobble.refresh_from_db()+    assert scrobble.visibility == "shared"+++@pytest.mark.django_db+def test_change_visibility_non_owner_gets_404(client):+    owner = get_user_model().objects.create_user(+        username="owner", password="testpass"+    )+    other = get_user_model().objects.create_user(+        username="other", password="testpass"+    )+    task = Task.objects.create(title="Test Task")+    scrobble = Scrobble.objects.create(+        task=task, media_type="Task", user=owner, visibility="private",+        timestamp=timezone.now(),+    )++    client.force_login(other)+    url = reverse("scrobbles:change-visibility", kwargs={"uuid": scrobble.uuid})+    response = client.post(url, {"visibility": "shared"})+    assert response.status_code == 404++    scrobble.refresh_from_db()+    assert scrobble.visibility == "private"+++@pytest.mark.django_db+def test_change_visibility_anonymous_redirects_to_login(client):+    user = get_user_model().objects.create_user(+        username="anontest", password="testpass"+    )+    task = Task.objects.create(title="Test Task")+    scrobble = Scrobble.objects.create(+        task=task, media_type="Task", user=user, visibility="private",+        timestamp=timezone.now(),+    )+    url = reverse("scrobbles:change-visibility", kwargs={"uuid": scrobble.uuid})+    response = client.post(url, {"visibility": "shared"})+    assert response.status_code == 302+    assert "/login/" in response.url+++@pytest.mark.django_db+def test_regenerate_share_token_invalidates_old_sqid(client):+    user = get_user_model().objects.create_user(+        username="regentest", password="testpass"+    )+    task = Task.objects.create(title="Test Task")+    scrobble = Scrobble.objects.create(+        task=task, media_type="Task", user=user, visibility="shared",+        timestamp=timezone.now(),+    )+    old_sqid = encode_scrobble_share(scrobble.id, scrobble.share_token_version)++    client.force_login(user)+    url = reverse("scrobbles:regenerate-share-token", kwargs={"uuid": scrobble.uuid})+    response = client.post(url)+    assert response.status_code == 302++    scrobble.refresh_from_db()+    assert scrobble.share_token_version == 1++    old_url = reverse("scrobbles:shared-detail", kwargs={"sqid": old_sqid})+    old_response = client.get(old_url)+    assert old_response.status_code == 404++    new_sqid = encode_scrobble_share(scrobble.id, scrobble.share_token_version)+    new_url = reverse("scrobbles:shared-detail", kwargs={"sqid": new_sqid})+    new_response = client.get(new_url)+    assert new_response.status_code == 200+++@pytest.mark.django_db+def test_share_analytics_owner_can_view(client):+    user = get_user_model().objects.create_user(+        username="analyticsuser", password="testpass"+    )+    task = Task.objects.create(title="Test Task")+    scrobble = Scrobble.objects.create(+        task=task, media_type="Task", user=user, visibility="shared",+        timestamp=timezone.now(),+    )++    client.force_login(user)+    url = reverse("scrobbles:share-analytics", kwargs={"uuid": scrobble.uuid})+    response = client.get(url)+    assert response.status_code == 200+++@pytest.mark.django_db+def test_share_analytics_non_owner_gets_404(client):+    owner = get_user_model().objects.create_user(+        username="analyticsowner", password="testpass"+    )+    other = get_user_model().objects.create_user(+        username="analyticsother", password="testpass"+    )+    task = Task.objects.create(title="Test Task")+    scrobble = Scrobble.objects.create(+        task=task, media_type="Task", user=owner, visibility="shared",+        timestamp=timezone.now(),+    )++    client.force_login(other)+    url = reverse("scrobbles:share-analytics", kwargs={"uuid": scrobble.uuid})+    response = client.get(url)+    assert response.status_code == 404+++@pytest.mark.django_db+def test_share_analytics_shows_view_logs(client):+    user = get_user_model().objects.create_user(+        username="analyticsviews", password="testpass"+    )+    task = Task.objects.create(title="Test Task")+    scrobble = Scrobble.objects.create(+        task=task, media_type="Task", user=user, visibility="shared",+        timestamp=timezone.now(),+    )++    sqid = encode_scrobble_share(scrobble.id, scrobble.share_token_version)+    share_url = reverse("scrobbles:shared-detail", kwargs={"sqid": sqid})+    client.get(share_url)+    client.get(share_url)++    client.force_login(user)+    url = reverse("scrobbles:share-analytics", kwargs={"uuid": scrobble.uuid})+    response = client.get(url)+    assert response.status_code == 200+    content = response.content.decode()+    assert "127.0.0.1" in content+    assert ShareViewLog.objects.filter(scrobble=scrobble).count() == 2