Skip to content

Commit

Permalink
Merge pull request #642 from hackforla/develop
Browse files Browse the repository at this point in the history
Develop into main
  • Loading branch information
LoTerence authored Feb 13, 2025
2 parents 8a44ce7 + b1df7ae commit a46376f
Show file tree
Hide file tree
Showing 28 changed files with 8,112 additions and 14,955 deletions.
3 changes: 0 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,5 @@ migrate:
db-shell:
docker compose exec django python manage.py shell

test-frontend:
docker compose run -T -w /code/frontend webpack npm run test

test-server:
docker exec django python manage.py test server.tests
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Civic Tech technology practitioners are a diverse and interdisciplinary group of
- [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/)
- [@axe-core/react](https://github.com/dequelabs/axe-core-npm/tree/develop/packages/react)
- [Sass](https://sass-lang.com/documentation)
- [Webpack](https://webpack.js.org/)
- [Vite](https://vite.dev/)
- [Babel](https://babeljs.io/)

#### Backend
Expand Down
1 change: 0 additions & 1 deletion backend/backend/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from django.conf import settings
from django.views.generic import TemplateView


catchall_dev = TemplateView.as_view(template_name="dev-mode.html")

catchall_prod = TemplateView.as_view(template_name="index.html")
Expand Down
33 changes: 29 additions & 4 deletions backend/ctj_api/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,42 @@ class OpportunityPermission(permissions.BasePermission):
Only the creator of an opportunity can edit it.
"""

def has_permission(self, request, view):
"""
Check global permissions for the request method.
"""
# Allow safe methods for all users
if request.method in permissions.SAFE_METHODS:
return True

# Only PM's can create opportunities
if request.method == "POST":
return getattr(request.user, "isProjectManager", False)

# For PUT and DELETE, defer to object-level permissions
if request.method in ["PUT", "DELETE"]:
return request.user.is_authenticated

return False

def has_object_permission(self, request, view, obj):
"""
Check object-level permissions for the request method.
"""
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True

if request.method == "POST":
return request.user.isProjectManager

# PUT permissions are only allowed to the PM that created the opportunity.
return obj.created_by == request.user
if request.method == "PUT":
return obj.created_by == request.user

# Any PM can delete any opportunity
if request.method == "DELETE":
return getattr(request.user, "isProjectManager", False)

return False


class UserDetailPermission(permissions.BasePermission):
Expand Down
3 changes: 0 additions & 3 deletions backend/ctj_api/tests.py

This file was deleted.

Empty file.
146 changes: 146 additions & 0 deletions backend/ctj_api/tests/test_api_endpoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
from rest_framework.test import APITestCase, APIClient
from rest_framework import status
from ctj_api.models import (
CommunityOfPractice,
Role,
Skill,
Project,
Opportunity,
CustomUser,
)


class APIBasicTests(APITestCase):
def setUp(self):
self.client = APIClient()

# Create a project manager user
self.pm_user = CustomUser.objects.create_user(
people_depot_user_id="unique_id_pm",
username="pm_user",
email="[email protected]",
password="password123",
isProjectManager=True,
)

# Create a regular user1
self.regular_user1 = CustomUser.objects.create_user(
people_depot_user_id="unique_id_1",
username="regular_user1",
email="[email protected]",
password="password123",
isProjectManager=False,
)

# Create a regular user2
self.regular_user2 = CustomUser.objects.create_user(
people_depot_user_id="unique_id_2",
username="regular_user2",
email="[email protected]",
password="password123",
isProjectManager=False,
)

# Authenticate with the PM user initially
self.client.force_authenticate(user=self.pm_user)

# Create test data
self.cop = CommunityOfPractice.objects.create(
practice_area="engineering", description="Engineering CoP"
)

self.role = Role.objects.create(
title="Developer", community_of_practice=self.cop
)

self.skill = Skill.objects.create(name="Python")

self.project = Project.objects.create(
people_depot_project_id="1234-abcd", name="Civic Tech Jobs"
)

self.opportunity = Opportunity.objects.create(
project=self.project,
role=self.role,
body="This is a test opportunity",
min_experience_required="junior",
min_hours_required=10,
work_environment="remote",
status="open",
created_by=self.pm_user,
)

def test_healthcheck(self):
"""Test the healthcheck endpoint."""
response = self.client.get("/api/healthcheck")
self.assertEqual(response.status_code, status.HTTP_200_OK)
response_json = response.json()
self.assertIn("uptime", response_json)

def test_read_only_community_of_practice(self):
"""Test that Communities of Practice can be listed."""
response = self.client.get("/api/communityOfPractice/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)
self.assertEqual(response.data[0]["practice_area"], "engineering")

def test_read_only_roles(self):
"""Test that roles can be listed."""
response = self.client.get("/api/roles/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)
self.assertEqual(response.data[0]["title"], "Developer")

def test_read_only_skills(self):
"""Test that skills can be listed."""
response = self.client.get("/api/skills/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)
self.assertEqual(response.data[0]["name"], "Python")

def test_read_only_projects(self):
"""Test that projects can be listed."""
response = self.client.get("/api/projects/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)
self.assertEqual(response.data[0]["name"], "Civic Tech Jobs")

def test_list_opportunities(self):
"""Test listing opportunities as an unauthenticated user."""
self.client.logout()
response = self.client.get("/api/opportunities/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertGreater(len(response.data), 0)

def test_create_opportunity_as_regular_user(self):
"""Test that a regular user cannot create an opportunity."""
self.client.force_authenticate(user=self.regular_user1)
payload = {
"project": str(self.project.id),
"role": str(self.role.id),
"body": "Unauthorized attempt to create an opportunity",
"min_experience_required": "senior",
"min_hours_required": 5,
"work_environment": "remote",
"status": "open",
}
response = self.client.post("/api/opportunities/", payload)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

# def test_user_can_access_own_details(self):
# """Test that a user can access their own details."""
# self.client.force_authenticate(user=self.regular_user1)
# response = self.client.get(f"/api/users/{self.regular_user1.id}/")
# self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_user_cannot_access_other_user_details(self):
"""Test that a user can't access another user's details."""
self.client.force_authenticate(user=self.regular_user1)
response = self.client.get(f"/api/users/{self.regular_user2.id}/")
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

def test_unauthenticated_user_cannot_access_user_details(self):
"""Test that unauthenticated users can't access user details."""
self.client.logout()
response = self.client.get(f"/api/users/{self.regular_user1.id}/")
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
1 change: 1 addition & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ version = "0.1.0"
description = ""
authors = ["Your Name <[email protected]>"]
readme = "../README.md"
package-mode = false

[tool.poetry.dependencies]
python = "^3.12"
Expand Down
2 changes: 1 addition & 1 deletion dev/dev.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ SQL_HOST=pgdb
SQL_PORT=5432
DATABASE=postgres

# Webpack
# Frontend
MODE=development
DEVTOOL=inline-source-map
2 changes: 1 addition & 1 deletion frontend/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default [
},
},
{
ignores: ["node_modules/", "*.config.js", "tests/__mocks__/*"],
ignores: ["node_modules/", "dist/", "*.config.js", "tests/__mocks__/*"],
},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
Expand Down
Loading

0 comments on commit a46376f

Please sign in to comment.