Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/run-e2e-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ jobs:
db_username: nest_user_e2e
env_file: .env.e2e-tests

- name: Seed e2e users
run: |
set -euo pipefail && set -a && source .env.e2e-tests && set +a
export DJANGO_DB_HOST=localhost
export DJANGO_REDIS_AUTH_ENABLED=False
export DJANGO_REDIS_HOST=localhost
export PYTHONPATH="${PWD}/src"
poetry run python manage.py e2e_seed_users
working-directory: backend

- name: Install frontend dependencies
uses: ./.github/actions/install-frontend-dependencies

Expand Down
1 change: 1 addition & 0 deletions backend/src/apps/e2e/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""E2E app package."""
10 changes: 10 additions & 0 deletions backend/src/apps/e2e/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""E2E app config."""

from django.apps import AppConfig


class E2EConfig(AppConfig):
"""E2E app config."""

default_auto_field = "django.db.models.BigAutoField"
name = "apps.e2e"
1 change: 1 addition & 0 deletions backend/src/apps/e2e/management/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Management package."""
1 change: 1 addition & 0 deletions backend/src/apps/e2e/management/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Management commands package."""
83 changes: 83 additions & 0 deletions backend/src/apps/e2e/management/commands/e2e_seed_users.py
Comment thread
Mr-Rahul-Paul marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Seed deterministic users for end-to-end tests."""

from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone

from apps.core.utils import index
from apps.github.models.user import User as GithubUser
from apps.mentorship.models import Mentee, Mentor
from apps.nest.models import User as NestUser
from apps.owasp.models.entity_member import EntityMember
from apps.owasp.models.project import Project

E2E_USERS = (
("e2e-mentee", "mentee"),
("e2e-mentor", "mentor"),
("e2e-user", ""),
)


class Command(BaseCommand):
help = "Seed e2e test users."

def handle(self, *_args, **_options) -> None:
"""Create GitHub, Nest, and mentorship users for e2e tests."""
if not settings.IS_E2E_ENVIRONMENT:
error_message = "This command can only run in the e2e environment."
raise CommandError(error_message)

now = timezone.now()
with index.disable_indexing():
for login, role in E2E_USERS:
github_user, _ = GithubUser.objects.get_or_create(
login=login,
defaults={
"created_at": now,
"email": f"{login}@example.com",
"name": login,
"node_id": f"e2e_node_{login}",
"updated_at": now,
},
)
nest_user, _ = NestUser.objects.get_or_create(
username=login,
defaults={
"email": f"{login}@example.com",
"github_user": github_user,
},
)
if nest_user.github_user_id != github_user.id:
nest_user.github_user = github_user
nest_user.save(update_fields=["github_user"])
if role == "mentor":
Mentor.objects.get_or_create(
github_user=github_user,
defaults={"nest_user": nest_user},
)
elif role == "mentee":
Mentee.objects.get_or_create(
github_user=github_user,
defaults={"nest_user": nest_user},
)
elif login == "e2e-user":
project, _ = Project.objects.get_or_create(
key="www-project-e2e",
defaults={"name": "E2E Project"},
)
membership, _ = EntityMember.objects.get_or_create(
entity_id=project.id,
entity_type=ContentType.objects.get_for_model(Project),
member_name=login,
role=EntityMember.Role.LEADER,
defaults={
"is_active": True,
"is_reviewed": True,
"member": github_user,
},
)
membership.is_active = True
membership.is_reviewed = True
membership.member = github_user
membership.save(update_fields=["is_active", "is_reviewed", "member"])
11 changes: 11 additions & 0 deletions backend/src/apps/e2e/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""E2E app URL configuration."""

from django.urls import path

from apps.e2e.views import e2e_login

app_name = "e2e"

urlpatterns = [
path("login/", e2e_login, name="login"),
]
46 changes: 46 additions & 0 deletions backend/src/apps/e2e/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""E2E-only views."""

import json

from django.conf import settings
from django.contrib.auth import login
from django.http import Http404, HttpRequest, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST

from apps.nest.models import User

E2E_ALLOWED_USERS = frozenset({"e2e-user", "e2e-mentor", "e2e-mentee"})


@csrf_exempt # NOSONAR
@require_POST
def e2e_login(request: HttpRequest) -> JsonResponse:
"""Log in a seeded e2e user and set the Django session cookie."""
if not settings.IS_E2E_ENVIRONMENT:
raise Http404

try:
payload = json.loads(request.body or b"{}")
except json.JSONDecodeError:
return JsonResponse({"message": "Invalid JSON.", "ok": False}, status=400)

if not isinstance(payload, dict):
return JsonResponse({"message": "Invalid JSON.", "ok": False}, status=400)

Comment thread
coderabbitai[bot] marked this conversation as resolved.
raw_username = payload.get("username")
if not isinstance(raw_username, str) or not raw_username.strip():
return JsonResponse({"message": "username is required.", "ok": False}, status=400)

username = raw_username.strip()
if username not in E2E_ALLOWED_USERS:
raise Http404

try:
user = User.objects.get(username=username)
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
except User.DoesNotExist as exc:
raise Http404 from exc

login(request, user)

return JsonResponse({"ok": True, "username": username})
8 changes: 8 additions & 0 deletions backend/src/settings/e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,11 @@ class E2E(Base):
SECURE_PROXY_SSL_HEADER = None # type: ignore[assignment] # Django accepts None to disable.
SECURE_SSL_REDIRECT = False
SESSION_COOKIE_SECURE = False

ALGOLIA = {
**Base.ALGOLIA,
"AUTO_INDEXING": False,
}

LOCAL_APPS = (*Base.LOCAL_APPS, "apps.e2e") # type: ignore[assignment]
INSTALLED_APPS = Base.DJANGO_APPS + Base.THIRD_PARTY_APPS + LOCAL_APPS # type: ignore[assignment]
4 changes: 4 additions & 0 deletions backend/src/settings/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,9 @@
path("integrations/slack/interactivity/", slack_request_handler),
]

if settings.IS_E2E_ENVIRONMENT:
urlpatterns += [path("e2e/", include("apps.e2e.urls"))]


if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
1 change: 1 addition & 0 deletions backend/tests/unit/apps/e2e/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for apps.e2e."""
1 change: 1 addition & 0 deletions backend/tests/unit/apps/e2e/management/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for apps.e2e management."""
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for apps.e2e management commands."""
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from unittest.mock import MagicMock, patch

import pytest
from django.core.management import call_command
from django.core.management.base import CommandError

from apps.e2e.management.commands.e2e_seed_users import E2E_USERS, Command


class TestE2ESeedUsersCommand:
def test_metadata(self):
assert Command.help == "Seed e2e test users."
assert E2E_USERS == (
("e2e-mentee", "mentee"),
("e2e-mentor", "mentor"),
("e2e-user", ""),
)

def test_requires_e2e_environment(self):
with (
patch(
"apps.e2e.management.commands.e2e_seed_users.settings.IS_E2E_ENVIRONMENT",
new=False,
),
pytest.raises(CommandError, match="e2e environment"),
):

Check warning on line 26 in backend/tests/unit/apps/e2e/management/commands/e2e_seed_users_test.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this exception test to have only one invocation possibly throwing an exception.

See more on https://sonarcloud.io/project/issues?id=OWASP_Nest&issues=AaBg8HnfAQev6bR8mq3f&open=AaBg8HnfAQev6bR8mq3f&pullRequest=5454
call_command(Command())

@patch("apps.e2e.management.commands.e2e_seed_users.index.disable_indexing")
@patch("apps.e2e.management.commands.e2e_seed_users.ContentType")
@patch("apps.e2e.management.commands.e2e_seed_users.EntityMember")
@patch("apps.e2e.management.commands.e2e_seed_users.Project")
@patch("apps.e2e.management.commands.e2e_seed_users.Mentee")
@patch("apps.e2e.management.commands.e2e_seed_users.Mentor")
@patch("apps.e2e.management.commands.e2e_seed_users.NestUser")
@patch("apps.e2e.management.commands.e2e_seed_users.GithubUser")
def test_creates_users(
self,
mock_github_user,
mock_nest_user,
mock_mentor,
mock_mentee,
mock_project,
mock_entity_member,
mock_content_type,
mock_disable_indexing,
):
github_user = MagicMock()
github_user.id = 10
nest_user = MagicMock()
nest_user.github_user_id = 10
project = MagicMock(id=1)
membership = MagicMock()
mock_github_user.objects.get_or_create.return_value = (github_user, True)
mock_nest_user.objects.get_or_create.return_value = (nest_user, True)
mock_project.objects.get_or_create.return_value = (project, True)
mock_entity_member.objects.get_or_create.return_value = (membership, True)

with patch(
"apps.e2e.management.commands.e2e_seed_users.settings.IS_E2E_ENVIRONMENT",
new=True,
):
call_command(Command())

mock_disable_indexing.assert_called_once()
assert mock_github_user.objects.get_or_create.call_count == 3
assert mock_nest_user.objects.get_or_create.call_count == 3
mock_mentor.objects.get_or_create.assert_called_once_with(
github_user=github_user,
defaults={"nest_user": nest_user},
)
mock_mentee.objects.get_or_create.assert_called_once_with(
github_user=github_user,
defaults={"nest_user": nest_user},
)
mock_project.objects.get_or_create.assert_called_once_with(
key="www-project-e2e",
defaults={"name": "E2E Project"},
)
mock_entity_member.objects.get_or_create.assert_called_once_with(
entity_id=project.id,
entity_type=mock_content_type.objects.get_for_model.return_value,
member_name="e2e-user",
role=mock_entity_member.Role.LEADER,
defaults={
"is_active": True,
"is_reviewed": True,
"member": github_user,
},
)
membership.save.assert_called_once_with(
update_fields=["is_active", "is_reviewed", "member"]
)
1 change: 1 addition & 0 deletions backend/tests/unit/apps/e2e/views/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for apps.e2e views."""
Loading
Loading