@@ -0,0 +1,301 @@
1 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Tests for the repository lifecycle UI: project creation with repo source, and repo settings."""
2 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
3 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from unittest.mock import MagicMock, patch
4 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
5 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import pytest
6 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.contrib.auth.models import User
7 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.test import Client
8 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
9 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.models import FossilRepository
10 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from organization.models import Team
11 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from projects.models import Project, ProjectTeam
12 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
13 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
14 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @pytest.fixture
15 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def fossil_repo_obj(sample_project):
16 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return the auto-created FossilRepository for sample_project."""
17 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return FossilRepository.objects.get(project=sample_project, deleted_at__isnull=True)
18 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
19 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
20 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @pytest.fixture
21 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def writer_user(db, admin_user, sample_project):
22 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """User with write access but not admin."""
23 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ writer = User.objects.create_user(username="writer", password="testpass123")
24 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ team = Team.objects.create(name="Writers", organization=sample_project.organization, created_by=admin_user)
25 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ team.members.add(writer)
26 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ProjectTeam.objects.create(project=sample_project, team=team, role="write", created_by=admin_user)
27 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return writer
28 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
29 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
30 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @pytest.fixture
31 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def writer_client(writer_user):
32 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ client = Client()
33 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ client.login(username="writer", password="testpass123")
34 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return client
35 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
36 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
37 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @pytest.fixture
38 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def admin_team_user(db, admin_user, sample_project):
39 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """User with admin team role on the sample project."""
40 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ admin_team_member = User.objects.create_user(username="projadmin", password="testpass123")
41 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ team = Team.objects.create(name="Admins", organization=sample_project.organization, created_by=admin_user)
42 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ team.members.add(admin_team_member)
43 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ProjectTeam.objects.create(project=sample_project, team=team, role="admin", created_by=admin_user)
44 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return admin_team_member
45 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
46 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
47 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @pytest.fixture
48 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def admin_team_client(admin_team_user):
49 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ client = Client()
50 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ client.login(username="projadmin", password="testpass123")
51 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return client
52 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
53 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
54 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Project Create Form Tests ---
55 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
56 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
57 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @pytest.mark.django_db
58 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ class TestProjectCreateForm:
59 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_create_form_shows_repo_source(self, admin_client):
60 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_client.get("/projects/create/")
61 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert response.status_code == 200
62 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ content = response.content.decode()
63 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert "repo_source" in content
64 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert "Create empty repository" in content
65 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert "Clone from Fossil URL" in content
66 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
67 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_create_empty_repo(self, admin_client, org):
68 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_client.post(
69 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "/projects/create/",
70 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"name": "Empty Repo", "visibility": "private", "repo_source": "empty"},
71 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
72 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert response.status_code == 302
73 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project = Project.objects.get(name="Empty Repo")
74 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert project is not None
75 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ fossil_repo = FossilRepository.objects.get(project=project)
76 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert fossil_repo.filename == f"{project.slug}.fossil"
77 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert fossil_repo.remote_url == ""
78 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
79 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_create_with_missing_clone_url_fails(self, admin_client, org):
80 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_client.post(
81 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "/projects/create/",
82 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"name": "Clone Fail", "visibility": "private", "repo_source": "fossil_url", "clone_url": ""},
83 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
84 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Form should re-render with errors, not redirect
85 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert response.status_code == 200
86 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ content = response.content.decode()
87 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert "Clone URL is required" in content
88 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
89 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @patch("projects.views._clone_fossil_repo")
90 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_create_clone_calls_helper(self, mock_clone, admin_client, org):
91 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_client.post(
92 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "/projects/create/",
93 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
94 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "name": "Cloned Repo",
95 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "visibility": "private",
96 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "repo_source": "fossil_url",
97 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "clone_url": "https://fossil-scm.org/home",
98 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
99 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
100 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert response.status_code == 302
101 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project = Project.objects.get(name="Cloned Repo")
102 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ mock_clone.assert_called_once()
103 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ call_args = mock_clone.call_args
104 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert call_args[0][1] == project
105 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert call_args[0][2] == "https://fossil-scm.org/home"
106 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
107 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_create_without_repo_source_defaults_to_empty(self, admin_client, org):
108 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_client.post(
109 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "/projects/create/",
110 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"name": "Default Source", "visibility": "private"},
111 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
112 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert response.status_code == 302
113 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project = Project.objects.get(name="Default Source")
114 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ fossil_repo = FossilRepository.objects.get(project=project)
115 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert fossil_repo.remote_url == ""
116 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
117 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_edit_form_does_not_show_repo_source(self, admin_client, sample_project):
118 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_client.get(f"/projects/{sample_project.slug}/edit/")
119 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert response.status_code == 200
120 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ content = response.content.decode()
121 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert "Repository Source" not in content
122 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
123 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
124 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Project Update Form Tests (no repo source fields) ---
125 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
126 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
127 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @pytest.mark.django_db
128 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ class TestProjectUpdateExcludesRepoSource:
129 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_update_preserves_project(self, admin_client, sample_project):
130 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_client.post(
131 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f"/projects/{sample_project.slug}/edit/",
132 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"name": "Updated Name", "visibility": "public"},
133 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
134 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert response.status_code == 302
135 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ sample_project.refresh_from_db()
136 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert sample_project.name == "Updated Name"
137 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
138 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
139 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Repo Settings View Tests ---
140 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
141 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
142 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @pytest.mark.django_db
143 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ class TestRepoSettingsAccess:
144 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_settings_denied_for_anon(self, client, sample_project, fossil_repo_obj):
145 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = client.get(f"/projects/{sample_project.slug}/fossil/settings/")
146 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Redirects to login for anon
147 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert response.status_code == 302
148 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
149 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_settings_denied_for_writer(self, writer_client, sample_project, fossil_repo_obj):
150 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = writer_client.get(f"/projects/{sample_project.slug}/fossil/settings/")
151 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert response.status_code == 403
152 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
153 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_settings_allowed_for_superuser(self, admin_client, sample_project, fossil_repo_obj):
154 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_client.get(f"/projects/{sample_project.slug}/fossil/settings/")
155 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert response.status_code == 200
156 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
157 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_settings_allowed_for_project_admin(self, admin_team_client, sample_project, fossil_repo_obj):
158 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_team_client.get(f"/projects/{sample_project.slug}/fossil/settings/")
159 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert response.status_code == 200
160 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
161 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
162 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @pytest.mark.django_db
163 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ class TestRepoSettingsContent:
164 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_settings_page_shows_filename(self, admin_client, sample_project, fossil_repo_obj):
165 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_client.get(f"/projects/{sample_project.slug}/fossil/settings/")
166 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ content = response.content.decode()
167 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert fossil_repo_obj.filename in content
168 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
169 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_settings_page_shows_remote_form(self, admin_client, sample_project, fossil_repo_obj):
170 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_client.get(f"/projects/{sample_project.slug}/fossil/settings/")
171 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ content = response.content.decode()
172 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert 'name="remote_url"' in content
173 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert "Save Remote" in content
174 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
175 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_settings_page_shows_clone_urls(self, admin_client, sample_project, fossil_repo_obj):
176 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_client.get(f"/projects/{sample_project.slug}/fossil/settings/")
177 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ content = response.content.decode()
178 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert "Clone URLs" in content
179 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
180 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_settings_page_shows_danger_zone(self, admin_client, sample_project, fossil_repo_obj):
181 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_client.get(f"/projects/{sample_project.slug}/fossil/settings/")
182 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ content = response.content.decode()
183 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert "Danger Zone" in content
184 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
185 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_settings_active_tab(self, admin_client, sample_project, fossil_repo_obj):
186 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_client.get(f"/projects/{sample_project.slug}/fossil/settings/")
187 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ content = response.content.decode()
188 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # The Settings tab should be active (has the active CSS classes)
189 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert "Settings" in content
190 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
191 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
192 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @pytest.mark.django_db
193 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ class TestRepoSettingsActions:
194 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_update_remote_url(self, admin_client, sample_project, fossil_repo_obj):
195 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_client.post(
196 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f"/projects/{sample_project.slug}/fossil/settings/",
197 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"action": "update_remote", "remote_url": "https://fossil-scm.org/home"},
198 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
199 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert response.status_code == 302
200 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ fossil_repo_obj.refresh_from_db()
201 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert fossil_repo_obj.remote_url == "https://fossil-scm.org/home"
202 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
203 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_clear_remote_url(self, admin_client, sample_project, fossil_repo_obj):
204 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ fossil_repo_obj.remote_url = "https://old-url.example.com"
205 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ fossil_repo_obj.save()
206 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_client.post(
207 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f"/projects/{sample_project.slug}/fossil/settings/",
208 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"action": "update_remote", "remote_url": ""},
209 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
210 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert response.status_code == 302
211 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ fossil_repo_obj.refresh_from_db()
212 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert fossil_repo_obj.remote_url == ""
213 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
214 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_update_remote_denied_for_writer(self, writer_client, sample_project, fossil_repo_obj):
215 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = writer_client.post(
216 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f"/projects/{sample_project.slug}/fossil/settings/",
217 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"action": "update_remote", "remote_url": "https://evil.example.com"},
218 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
219 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert response.status_code == 403
220 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ fossil_repo_obj.refresh_from_db()
221 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert fossil_repo_obj.remote_url != "https://evil.example.com"
222 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
223 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
224 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Nav Tab Tests ---
225 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
226 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
227 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @pytest.mark.django_db
228 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ class TestProjectNavSettings:
229 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_settings_tab_visible_for_admin(self, admin_client, sample_project, fossil_repo_obj):
230 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """The Settings tab should appear in the nav for admins on fossil views."""
231 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = admin_client.get(f"/projects/{sample_project.slug}/fossil/settings/")
232 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ content = response.content.decode()
233 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert "Settings" in content
234 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert f"/projects/{sample_project.slug}/fossil/settings/" in content
235 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
236 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
237 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Signal Guard Tests ---
238 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
239 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
240 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @pytest.mark.django_db
241 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ class TestSignalExistingFileGuard:
242 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @patch("fossil.cli.FossilCLI")
243 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_signal_skips_init_when_file_exists(self, mock_cli_cls, org, admin_user, tmp_path):
244 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """When a .fossil file already exists, the signal should skip fossil init."""
245 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ mock_cli = MagicMock()
246 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ mock_cli.is_available.return_value = True
247 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ mock_cli_cls.return_value = mock_cli
248 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
249 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Create the project -- the signal fires
250 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project = Project.objects.create(name="Pre-existing", organization=org, created_by=admin_user)
251 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
252 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # The signal creates a FossilRepository record. Since the .fossil file won't exist
253 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # on disk in tests (no real FOSSIL_DATA_DIR), the signal will attempt init via CLI.
254 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # The key assertion is that the record was created and the code path doesn't crash.
255 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert FossilRepository.objects.filter(project=project).exists()
256 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
257 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_signal_creates_repo_record(self, org, admin_user):
258 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """The signal creates a FossilRepository record when a Project is created."""
259 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project = Project.objects.create(name="Signal Test", organization=org, created_by=admin_user)
260 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert FossilRepository.objects.filter(project=project).exists()
261 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ fossil_repo = FossilRepository.objects.get(project=project)
262 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert fossil_repo.filename == f"{project.slug}.fossil"
263 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
264 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
265 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Form Validation Tests ---
266 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
267 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
268 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @pytest.mark.django_db
269 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ class TestProjectFormValidation:
270 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_form_valid_with_empty_source(self):
271 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from projects.forms import ProjectForm
272 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
273 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ form = ProjectForm(data={"name": "Test", "visibility": "private", "repo_source": "empty"})
274 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert form.is_valid()
275 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
276 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_form_valid_with_clone_url(self):
277 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from projects.forms import ProjectForm
278 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
279 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ form = ProjectForm(
280 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data={
281 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "name": "Test Clone",
282 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "visibility": "private",
283 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "repo_source": "fossil_url",
284 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "clone_url": "https://fossil-scm.org/home",
285 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
286 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
287 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert form.is_valid()
288 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
289 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def test_form_invalid_clone_without_url(self):
290 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from projects.forms import ProjectForm
291 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
292 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ form = ProjectForm(
293 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data={
294 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "name": "No URL",
295 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "visibility": "private",
296 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "repo_source": "fossil_url",
297 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "clone_url": "",
298 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
299 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
300 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert not form.is_valid()
301 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assert "clone_url" in form.errors