FossilRepo

fossilrepo / tests / test_starring.py
Source Blame History 181 lines
c588255… ragelink 1 """Tests for Project Starring: model, toggle view, explore page, and admin."""
c588255… ragelink 2
c588255… ragelink 3 import pytest
c588255… ragelink 4 from django.contrib.auth.models import User
c588255… ragelink 5 from django.db import IntegrityError
c588255… ragelink 6
c588255… ragelink 7 from projects.models import Project, ProjectStar
c588255… ragelink 8
c588255… ragelink 9 # --- Model Tests ---
c588255… ragelink 10
c588255… ragelink 11
c588255… ragelink 12 @pytest.mark.django_db
c588255… ragelink 13 class TestProjectStarModel:
c588255… ragelink 14 def test_create_star(self, admin_user, sample_project):
c588255… ragelink 15 star = ProjectStar.objects.create(user=admin_user, project=sample_project)
c588255… ragelink 16 assert star.pk is not None
c588255… ragelink 17 assert star.user == admin_user
c588255… ragelink 18 assert star.project == sample_project
c588255… ragelink 19 assert str(star) == f"{admin_user} starred {sample_project}"
c588255… ragelink 20
c588255… ragelink 21 def test_unique_constraint(self, admin_user, sample_project):
c588255… ragelink 22 ProjectStar.objects.create(user=admin_user, project=sample_project)
c588255… ragelink 23 with pytest.raises(IntegrityError):
c588255… ragelink 24 ProjectStar.objects.create(user=admin_user, project=sample_project)
c588255… ragelink 25
c588255… ragelink 26 def test_star_count_property(self, admin_user, viewer_user, sample_project):
c588255… ragelink 27 assert sample_project.star_count == 0
c588255… ragelink 28 ProjectStar.objects.create(user=admin_user, project=sample_project)
c588255… ragelink 29 assert sample_project.star_count == 1
c588255… ragelink 30 ProjectStar.objects.create(user=viewer_user, project=sample_project)
c588255… ragelink 31 assert sample_project.star_count == 2
c588255… ragelink 32
c588255… ragelink 33 def test_star_cascade_on_user_delete(self, org, admin_user):
c588255… ragelink 34 """Stars cascade-delete when the user is deleted."""
c588255… ragelink 35 temp_user = User.objects.create_user(username="tempuser", password="testpass123")
c588255… ragelink 36 project = Project.objects.create(name="Cascade Test", organization=org, created_by=admin_user)
c588255… ragelink 37 ProjectStar.objects.create(user=temp_user, project=project)
c588255… ragelink 38 temp_user.delete()
c588255… ragelink 39 assert ProjectStar.objects.count() == 0
c588255… ragelink 40
c588255… ragelink 41 def test_multiple_users_can_star_same_project(self, admin_user, viewer_user, sample_project):
c588255… ragelink 42 ProjectStar.objects.create(user=admin_user, project=sample_project)
c588255… ragelink 43 ProjectStar.objects.create(user=viewer_user, project=sample_project)
c588255… ragelink 44 assert ProjectStar.objects.filter(project=sample_project).count() == 2
c588255… ragelink 45
c588255… ragelink 46
c588255… ragelink 47 # --- Toggle Star View Tests ---
c588255… ragelink 48
c588255… ragelink 49
c588255… ragelink 50 @pytest.mark.django_db
c588255… ragelink 51 class TestToggleStarView:
c588255… ragelink 52 def test_star_project(self, admin_client, admin_user, sample_project):
c588255… ragelink 53 response = admin_client.post(f"/projects/{sample_project.slug}/star/")
c588255… ragelink 54 assert response.status_code == 302
c588255… ragelink 55 assert ProjectStar.objects.filter(user=admin_user, project=sample_project).exists()
c588255… ragelink 56
c588255… ragelink 57 def test_unstar_project(self, admin_client, admin_user, sample_project):
c588255… ragelink 58 ProjectStar.objects.create(user=admin_user, project=sample_project)
c588255… ragelink 59 response = admin_client.post(f"/projects/{sample_project.slug}/star/")
c588255… ragelink 60 assert response.status_code == 302
c588255… ragelink 61 assert not ProjectStar.objects.filter(user=admin_user, project=sample_project).exists()
c588255… ragelink 62
c588255… ragelink 63 def test_star_htmx_returns_partial(self, admin_client, admin_user, sample_project):
c588255… ragelink 64 response = admin_client.post(f"/projects/{sample_project.slug}/star/", HTTP_HX_REQUEST="true")
c588255… ragelink 65 assert response.status_code == 200
c588255… ragelink 66 content = response.content.decode()
c588255… ragelink 67 assert "star-button" in content
c588255… ragelink 68 assert "Starred" in content # Just starred it
c588255… ragelink 69 assert "<!DOCTYPE html>" not in content
c588255… ragelink 70
c588255… ragelink 71 def test_unstar_htmx_returns_partial(self, admin_client, admin_user, sample_project):
c588255… ragelink 72 ProjectStar.objects.create(user=admin_user, project=sample_project)
c588255… ragelink 73 response = admin_client.post(f"/projects/{sample_project.slug}/star/", HTTP_HX_REQUEST="true")
c588255… ragelink 74 assert response.status_code == 200
c588255… ragelink 75 content = response.content.decode()
c588255… ragelink 76 assert "Star" in content
c588255… ragelink 77
c588255… ragelink 78 def test_star_denied_for_anon(self, client, sample_project):
c588255… ragelink 79 response = client.post(f"/projects/{sample_project.slug}/star/")
c588255… ragelink 80 assert response.status_code == 302 # Redirect to login
c588255… ragelink 81
c588255… ragelink 82 def test_star_404_for_deleted_project(self, admin_client, admin_user, sample_project):
c588255… ragelink 83 sample_project.soft_delete(user=admin_user)
c588255… ragelink 84 response = admin_client.post(f"/projects/{sample_project.slug}/star/")
c588255… ragelink 85 assert response.status_code == 404
c588255… ragelink 86
c588255… ragelink 87 def test_star_shows_on_project_detail(self, admin_client, admin_user, sample_project):
c588255… ragelink 88 ProjectStar.objects.create(user=admin_user, project=sample_project)
c588255… ragelink 89 response = admin_client.get(f"/projects/{sample_project.slug}/")
c588255… ragelink 90 assert response.status_code == 200
c588255… ragelink 91 assert response.context["is_starred"] is True
c588255… ragelink 92
c588255… ragelink 93 def test_unstarred_shows_on_project_detail(self, admin_client, admin_user, sample_project):
c588255… ragelink 94 response = admin_client.get(f"/projects/{sample_project.slug}/")
c588255… ragelink 95 assert response.status_code == 200
c588255… ragelink 96 assert response.context["is_starred"] is False
c588255… ragelink 97
c588255… ragelink 98
c588255… ragelink 99 # --- Explore View Tests ---
c588255… ragelink 100
c588255… ragelink 101
c588255… ragelink 102 @pytest.mark.django_db
c588255… ragelink 103 class TestExploreView:
c588255… ragelink 104 def test_explore_accessible_to_anon(self, client, org, admin_user):
c588255… ragelink 105 Project.objects.create(name="Public Project", organization=org, visibility="public", created_by=admin_user)
c588255… ragelink 106 response = client.get("/explore/")
c588255… ragelink 107 assert response.status_code == 200
c588255… ragelink 108 assert "Public Project" in response.content.decode()
c588255… ragelink 109
c588255… ragelink 110 def test_explore_anon_only_sees_public(self, client, org, admin_user):
c588255… ragelink 111 Project.objects.create(name="Public One", organization=org, visibility="public", created_by=admin_user)
c588255… ragelink 112 Project.objects.create(name="Internal One", organization=org, visibility="internal", created_by=admin_user)
c588255… ragelink 113 Project.objects.create(name="Private One", organization=org, visibility="private", created_by=admin_user)
c588255… ragelink 114 response = client.get("/explore/")
c588255… ragelink 115 content = response.content.decode()
c588255… ragelink 116 assert "Public One" in content
c588255… ragelink 117 assert "Internal One" not in content
c588255… ragelink 118 assert "Private One" not in content
c588255… ragelink 119
c588255… ragelink 120 def test_explore_authenticated_sees_public_and_internal(self, admin_client, org, admin_user):
c588255… ragelink 121 Project.objects.create(name="Public Two", organization=org, visibility="public", created_by=admin_user)
c588255… ragelink 122 Project.objects.create(name="Internal Two", organization=org, visibility="internal", created_by=admin_user)
c588255… ragelink 123 Project.objects.create(name="Private Two", organization=org, visibility="private", created_by=admin_user)
c588255… ragelink 124 response = admin_client.get("/explore/")
c588255… ragelink 125 # Check the explore queryset in context (not full page content, which includes sidebar)
c588255… ragelink 126 explore_project_names = [p.name for p in response.context["projects"]]
c588255… ragelink 127 assert "Public Two" in explore_project_names
c588255… ragelink 128 assert "Internal Two" in explore_project_names
c588255… ragelink 129 assert "Private Two" not in explore_project_names
c588255… ragelink 130
c588255… ragelink 131 def test_explore_sort_by_name(self, client, org, admin_user):
c588255… ragelink 132 Project.objects.create(name="Zebra", organization=org, visibility="public", created_by=admin_user)
c588255… ragelink 133 Project.objects.create(name="Alpha", organization=org, visibility="public", created_by=admin_user)
c588255… ragelink 134 response = client.get("/explore/?sort=name")
c588255… ragelink 135 content = response.content.decode()
c588255… ragelink 136 assert content.index("Alpha") < content.index("Zebra")
c588255… ragelink 137
c588255… ragelink 138 def test_explore_sort_by_stars(self, client, org, admin_user):
c588255… ragelink 139 p1 = Project.objects.create(name="Less Stars", organization=org, visibility="public", created_by=admin_user)
c588255… ragelink 140 p2 = Project.objects.create(name="More Stars", organization=org, visibility="public", created_by=admin_user)
c588255… ragelink 141 user1 = User.objects.create_user(username="u1", password="testpass123")
c588255… ragelink 142 user2 = User.objects.create_user(username="u2", password="testpass123")
c588255… ragelink 143 ProjectStar.objects.create(user=user1, project=p2)
c588255… ragelink 144 ProjectStar.objects.create(user=user2, project=p2)
c588255… ragelink 145 ProjectStar.objects.create(user=user1, project=p1)
c588255… ragelink 146 response = client.get("/explore/?sort=stars")
c588255… ragelink 147 content = response.content.decode()
c588255… ragelink 148 assert content.index("More Stars") < content.index("Less Stars")
c588255… ragelink 149
c588255… ragelink 150 def test_explore_sort_by_recent(self, client, org, admin_user):
c588255… ragelink 151 Project.objects.create(name="Old Project", organization=org, visibility="public", created_by=admin_user)
c588255… ragelink 152 Project.objects.create(name="New Project", organization=org, visibility="public", created_by=admin_user)
c588255… ragelink 153 response = client.get("/explore/?sort=recent")
c588255… ragelink 154 content = response.content.decode()
c588255… ragelink 155 assert content.index("New Project") < content.index("Old Project")
c588255… ragelink 156
c588255… ragelink 157 def test_explore_search(self, client, org, admin_user):
c588255… ragelink 158 Project.objects.create(name="Fossil SCM", organization=org, visibility="public", created_by=admin_user)
c588255… ragelink 159 Project.objects.create(name="Other Project", organization=org, visibility="public", created_by=admin_user)
c588255… ragelink 160 response = client.get("/explore/?search=fossil")
c588255… ragelink 161 content = response.content.decode()
c588255… ragelink 162 assert "Fossil SCM" in content
c588255… ragelink 163 assert "Other Project" not in content
c588255… ragelink 164
c588255… ragelink 165 def test_explore_excludes_deleted_projects(self, client, org, admin_user):
c588255… ragelink 166 project = Project.objects.create(name="Deleted Project", organization=org, visibility="public", created_by=admin_user)
c588255… ragelink 167 project.soft_delete(user=admin_user)
c588255… ragelink 168 response = client.get("/explore/")
c588255… ragelink 169 assert "Deleted Project" not in response.content.decode()
c588255… ragelink 170
c588255… ragelink 171 def test_explore_starred_ids_for_authenticated_user(self, admin_client, admin_user, org):
c588255… ragelink 172 p1 = Project.objects.create(name="Starred P", organization=org, visibility="public", created_by=admin_user)
c588255… ragelink 173 Project.objects.create(name="Unstarred P", organization=org, visibility="public", created_by=admin_user)
c588255… ragelink 174 ProjectStar.objects.create(user=admin_user, project=p1)
c588255… ragelink 175 response = admin_client.get("/explore/")
c588255… ragelink 176 assert p1.id in response.context["starred_ids"]
c588255… ragelink 177
c588255… ragelink 178 def test_explore_sidebar_link_exists(self, admin_client):
c588255… ragelink 179 response = admin_client.get("/dashboard/")
c588255… ragelink 180 assert response.status_code == 200
c588255… ragelink 181 assert "/explore/" in response.content.decode()

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button