FossilRepo

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

Keyboard Shortcuts

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